Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
return;
}
Node nodeToInsertAfter = findNodeToInsertAfter(n);
nodeToInsertAfter = addMarker(funType, nodeToInsertAfter, null);
TreeSet<ObjectType> stuff = Sets.newTreeSet(ALPHA);
Iterables.addAll(stuff, funType.getAllImplementedInterfaces());
for (ObjectType interfaceType : stuff) {
nodeToInsertAfter =
addMarker(funType, nodeToInsertAfter, interfaceType);
}
}
private Node addMarker(
FunctionType funType,
Node nodeToInsertAfter,
@Nullable ObjectType interfaceType) {
if (funType.getSource() == null) {
return nodeToInsertAfter;
}
String className = NodeUtil.getFunctionName(funType.getSource());
// This can happen with anonymous classes declared with the type
// {@code Function}.
if (className == null) {
return nodeToInsertAfter;
}
Node classNode = NodeUtil.newQualifiedNameNode(className, -1, -1);
Node marker = Node.newString(
interfaceType == null ?
"instance_of__" + className :
"implements__" + interfaceType.getReferenceName());
Node assign = new Node(Token.EXPR_RESULT, new Node(Token.ASSIGN,
new Node(Token.GETELEM,
new Node(Token.GETPROP,
classNode,
Node.newString("prototype")), marker),
new Node(Token.TRUE)));
nodeToInsertAfter.getParent().addChildAfter(assign, nodeToInsertAfter);
compiler.reportCodeChange();
nodeToInsertAfter = assign;
return nodeToInsertAfter;
}
/**
* Find the node to insert the markers after. Typically, this node
* corresponds to the constructor declaration, but we want to skip any of
* the white-listed function calls.
*
* @param n the constructor function node
* @return the node to insert after
*/
private Node findNodeToInsertAfter(Node n) {
Node nodeToInsertAfter = findEnclosingConstructorDeclaration(n);
Node next = nodeToInsertAfter.getNext();
while (next != null && isClassDefiningCall(next)) {
nodeToInsertAfter = next;
next = nodeToInsertAfter.getNext();
}
return nodeToInsertAfter;
}
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
private Node findEnclosingConstructorDeclaration(Node n) {
while (n.getParent().getType() != Token.SCRIPT &&
n.getParent().getType() != Token.BLOCK) {
n = n.getParent();
}
return n;
}
private boolean isClassDefiningCall(Node next) {
return NodeUtil.isExprCall(next) &&
compiler.getCodingConvention().getClassesDefinedByCall(
next.getFirstChild()) != null;
}
}
/**
* Insert calls to the runtime type checking function {@code checkType}, which
* takes an expression to check and a list of checkers (one of which must
* match). It returns the expression back to facilitate checking of return
* values. We have checkers for value types, class types (user-defined and
* externed), and interface types.
*/
private static class AddChecks
extends NodeTraversal.AbstractPostOrderCallback {
private final AbstractCompiler compiler;
private AddChecks(AbstractCompiler compiler) {
this.compiler = compiler;
}
checkNode = new Node(Token.EXPR_RESULT, checkNode);
if (insertionPoint == null) {
block.addChildToFront(checkNode);
} else {
block.addChildAfter(checkNode, insertionPoint);
}
compiler.reportCodeChange();
paramName = paramName.getNext();
insertionPoint = checkNode;
}
}
private void visitReturn(NodeTraversal t, Node n) {
Node function = t.getEnclosingFunction();
FunctionType funType = (FunctionType) function.getJSType();
Node retValue = n.getFirstChild();
if (retValue == null) {
return;
}
Node checkNode = createCheckTypeCallNode(
funType.getReturnType(), retValue.cloneTree());
if (checkNode == null) {
return;
}
n.replaceChild(retValue, checkNode);
compiler.reportCodeChange();
}
/**
* Creates a function call to check that the given expression matches the
* given type at runtime.
*
* <p>For example, if the type is {@code (string|Foo)}, the function call is
* {@code checkType(expr, [valueChecker('string'), classChecker('Foo')])}.
*
* @return the function
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> addVar(String name) {
int vIndex = itsVariableNames.get(name, -1);
if (vIndex != -1) {
// There's already a variable or parameter with this name.
if (vIndex >= varStart) {
Object v = itsConst.get(vIndex);
if (v != null)
return DUPLICATE_CONST;
else
return DUPLICATE_VAR;
} else
return DUPLICATE_PARAMETER;
}
int index = itsVariables.size();
itsVariables.add(name);
itsConst.add(null);
itsVariableNames.put(name, index);
return NO_DUPLICATE;
}
public final boolean addConst(String name) {
int vIndex = itsVariableNames.get(name, -1);
if (vIndex != -1) {
// There's already a variable or parameter with this name.
return false;
}
int index = itsVariables.size();
itsVariables.add(name);
itsConst.add(name);
itsVariableNames.put(name, index);
return true;
}
public final void removeParamOrVar(String name) {
int i = itsVariableNames.get(name, -1);
if (i != -1) {
itsVariables.remove(i);
itsVariableNames.remove(name);
ObjToIntMap.Iterator iter = itsVariableNames.newIterator();
for (iter.start(); !iter.done(); iter.next()) {
int v = iter.getValue();
if (v > i) {
iter.setValue(v - 1);
}
}
}
}
public final Object getCompilerData()
{
return compilerData;
}
public final void setCompilerData(Object data)
{
if (data == null) throw new IllegalArgumentException();
// Can only call once
if (compilerData != null) throw new IllegalStateException();
compilerData = data;
}
private int encodedSourceStart;
private int encodedSourceEnd;
private String sourceName;
private int baseLineno = -1;
private int endLineno = -1;
private ObjArray functions;
private ObjArray regexps;
// a list of the formal parameters and local variables
private ObjArray itsVariables
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>;
}
@Override
public List<GraphvizNode> getGraphvizNodes() {
List<GraphvizNode> nodeList =
Lists.newArrayListWithCapacity(nodes.size());
for (LinkedDirectedGraphNode<N, E> node : nodes.values()) {
nodeList.add(node);
}
return nodeList;
}
@Override
public String getName() {
return "LinkedGraph";
}
@Override
public boolean isDirected() {
return true;
}
@Override
public Collection<GraphNode<N, E>> getNodes() {
return Collections.<GraphNode<N, E>>unmodifiableCollection(nodes.values());
}
@Override
public List<GraphNode<N, E>> getNeighborNodes(N value) {
DiGraphNode<N, E> node = getDirectedGraphNode(value);
return getNeighborNodes(node);
}
public List<GraphNode<N, E>> getNeighborNodes(DiGraphNode<N, E> node) {
List<GraphNode<N, E>> result = Lists.newArrayList();
for (Iterator<GraphNode<N, E>> i =
((LinkedDirectedGraphNode<N, E>) node).neighborIterator();i.hasNext();) {
result.add(i.next());
}
return result;
}
@Override
public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) {
LinkedDirectedGraphNode<N, E> node = nodes.get(value);
Preconditions.checkNotNull(node);
return node.neighborIterator();
}
@Override
public List<GraphEdge<N, E>> getEdges() {
List<GraphEdge<N, E>> result = Lists.newArrayList();
for (DiGraphNode<N, E> node : nodes.values()) {
for (DiGraphEdge<N, E> edge : node.getOutEdges()) {
result.add(edge);
}
}
return Collections.unmodifiableList(result);
}
@Override
public int getNodeDegree(N value) {
DiGraphNode<N, E> node = getNodeOrFail(value);
return node.getInEdges().size() + node.getOutEdges().size();
}
/**
* A directed graph node that stores outgoing
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> edges and incoming edges as an
* list within the node itself.
*/
static class LinkedDirectedGraphNode<N, E> implements DiGraphNode<N, E>,
GraphvizNode {
List<DiGraphEdge<N, E>> inEdgeList = Lists.newArrayList();
List<DiGraphEdge<N, E>> outEdgeList =
Lists.newArrayList();
protected final N value;
protected Annotation annotation;
protected int id;
private static int totalNodes = 0;
/**
* Constructor
*
* @param nodeValue Node's value.
*/
LinkedDirectedGraphNode(N nodeValue) {
this.value = nodeValue;
this.id = totalNodes++;
}
@Override
public N getValue() {
return value;
}
@SuppressWarnings("unchecked")
@Override
public <A extends Annotation> A getAnnotation() {
return (A) annotation;
}
@Override
public void setAnnotation(Annotation data) {
annotation = data;
}
@Override
public String getColor() {
return "white";
}
@Override
public String getId() {
return "LDN" + id;
}
@Override
public String getLabel() {
return value != null ? value.toString() : "null";
}
@Override
public String toString() {
return getLabel();
}
@Override
public List<DiGraphEdge<N, E>> getInEdges() {
return inEdgeList;
}
@Override
public List<DiGraphEdge<N, E>> getOutEdges() {
return outEdgeList;
}
private Iterator<GraphNode<N, E>> neighborIterator() {
return new NeighborIterator();
}
private class NeighborIterator implements Iterator<GraphNode<N, E>> {
private final Iterator<DiGraphEdge<N, E>> in = inEdgeList.iterator();
private final Iterator<DiGraphEdge<N, E>> out = outEdgeList.iterator();
@Override
public boolean hasNext() {
return in.hasNext() || out.hasNext();
}
@Override
public GraphNode<N, E> next() {
boolean isOut = !in.hasNext();
Iterator<DiGraphEdge<N, E>> curIterator = isOut ? out : in
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>;
DiGraphEdge<N, E> s = curIterator.next();
return isOut ? s.getDestination() : s.getSource();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported.");
}
}
}
/**
* A directed graph edge that stores the source and destination nodes at each
* edge.
*/
static class LinkedDirectedGraphEdge<N, E> implements DiGraphEdge<N, E>,
GraphvizEdge {
private DiGraphNode<N, E> sourceNode;
private DiGraphNode<N, E> destNode;
protected final E value;
protected Annotation annotation;
/**
* Constructor.
*
* @param edgeValue Edge Value.
*/
LinkedDirectedGraphEdge(DiGraphNode<N, E> sourceNode,
E edgeValue, DiGraphNode<N, E> destNode) {
this.value = edgeValue;
this.sourceNode = sourceNode;
this.destNode = destNode;
}
@Override
public DiGraphNode<N, E> getSource() {
return sourceNode;
}
@Override
public DiGraphNode<N, E> getDestination() {
return destNode;
}
@Override
public void setDestination(DiGraphNode<N, E> node) {
destNode = node;
}
@Override
public void setSource(DiGraphNode<N, E> node) {
sourceNode = node;
}
@Override
public E getValue() {
return value;
}
@SuppressWarnings("unchecked")
@Override
public <A extends Annotation> A getAnnotation() {
return (A) annotation;
}
@Override
public void setAnnotation(Annotation data) {
annotation = data;
}
@Override
public String getColor() {
return "black";
}
@Override
public String getLabel() {
return value != null ? value.toString() : "null";
}
@Override
public String getNode1Id() {
return ((LinkedDirectedGraphNode<N, E>) sourceNode).getId();
}
@Override
public String getNode2Id() {
return ((LinkedDirectedGraphNode<N, E>) destNode).getId();
}
@Override
public String toString()
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> extends NodeTraversal.AbstractPostOrderCallback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
/**
*
* In the AST that Rhino gives us, it needs to make a distinction
* between jsdoc on the object literal node and jsdoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
@SuppressWarnings("fallthrough")
public void visit(NodeTraversal t, Node n, Node parent) {
int nType = n.getType();
switch (nType) {
case Token.STRING:
// There are only two cases where a string token
// may be a variable reference: The right side of a GETPROP
// or an OBJECTLIT key.
if (parent.getType() != Token.OBJECTLIT &&
parent.getType() != Token.GETPROP) {
break;
}
// fall-through
case Token.NAME:
String nString = n.getString();
if (nType == Token.NAME &&
n.getParent().getType() == Token.CALL &&
"eval".equals(nString)) {
n.putBooleanProp(Node.DIRECT_EVAL, true);
}
if (NodeUtil.isConstantByConvention(convention, n, parent)) {
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
break;
case Token.FUNCTION:
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null) {
// Look for the info on other nodes.
if (parent.getType() == Token.ASSIGN) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.getType() == Token.NAME) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
// Compute which function parameters are
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter(arg) ||
typeExpr != null && typeExpr.isVarArgs()) {
arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true);
}
}
break;
case Token.OBJECTLIT:
if (n.getType() == Token.OBJECTLIT) {
for (Node key = n.getFirstChild();
key != null; key = key.getNext().getNext()) {
Node value = key.getNext();
if (key.getJSDocInfo() != null &&
key.getNext().getType() == Token.FUNCTION) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
}
break;
}
// TODO(johnlenz): Determine if it is possible to simply use the javadoc
// everywhere rather than use IS_DISPATCHER.
/*
* Translate dispatcher info into the property expected node.
*/
if (n.getJSDocInfo() != null && n.getJSDocInfo().isJavaDispatch()) {
if (n.getType() == Token.ASSIGN) {
Node fnNode = n.getLastChild();
Preconditions.checkState(fnNode.getType() == Token.FUNCTION);
fnNode.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
}
}
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>instance, implemented);
}
}
}
}
}
/**
* Report a type mismatch
*/
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSType required) {
mismatch(t.getSourceName(), n, msg, found, required);
}
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSTypeNative required) {
mismatch(t, n, msg, found, getNativeType(required));
}
private void mismatch(String sourceName, Node n,
String msg, JSType found, JSType required) {
registerMismatch(found, required);
if (shouldReport) {
compiler.report(
JSError.make(sourceName, n, TYPE_MISMATCH_WARNING,
formatFoundRequired(msg, found, required)));
}
}
private void registerMismatch(JSType found, JSType required) {
// Don't register a mismatch for differences in null or undefined or if the
// code didn't downcast.
found = found.restrictByNotNullOrUndefined();
required = required.restrictByNotNullOrUndefined();
if (found.canAssignTo(required) || required.canAssignTo(found)) {
return;
}
mismatches.add(new TypeMismatch(found, required));
if (found instanceof FunctionType &&
required instanceof FunctionType) {
FunctionType fnTypeA = ((FunctionType) found);
FunctionType fnTypeB = ((FunctionType) required);
Iterator<Node> paramItA = fnTypeA.getParameters().iterator();
Iterator<Node> paramItB = fnTypeB.getParameters().iterator();
while (paramItA.hasNext() && paramItB.hasNext()) {
registerIfMismatch(paramItA.next().getJSType(),
paramItB.next().getJSType());
}
registerIfMismatch(fnTypeA.getReturnType(), fnTypeB.getReturnType());
}
}
private void registerIfMismatch(JSType found, JSType required) {
if (found != null && required != null &&
!found.canAssignTo(required)) {
registerMismatch(found, required);
}
}
/**
* Formats a found/required error message.
*/
private String formatFoundRequired(String description, JSType
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> not belong to this scope, but are assigned
* in this scope.
*/
private final Multimap<Scope, Var> assignedOuterLocalVars =
HashMultimap.create();
/**
* Vars that we should not map out type flow for.
*/
private final Set<String> unflowableVarNames = Sets.newHashSet();
TypeInference(AbstractCompiler compiler, ControlFlowGraph<Node> cfg,
ReverseAbstractInterpreter reverseInterpreter,
Scope functionScope,
Map<String, AssertionFunctionSpec> assertionFunctionsMap) {
this(compiler, cfg, reverseInterpreter, functionScope,
assertionFunctionsMap, ImmutableSet.<Var>of());
}
/**
* @param unflowableVars Do not do infer flow on the types of these vars.
* @param assertionFunctionsMap
*/
// TODO(nicksantos): Create a builder for this class.
TypeInference(AbstractCompiler compiler, ControlFlowGraph<Node> cfg,
ReverseAbstractInterpreter reverseInterpreter,
Scope functionScope,
Map<String, AssertionFunctionSpec> assertionFunctionsMap,
Collection<Var> unflowableVars) {
super(cfg, new LinkedFlowScope.FlowScopeJoinOp());
this.compiler = compiler;
this.registry = compiler.getTypeRegistry();
this.reverseInterpreter = reverseInterpreter;
this.syntacticScope = functionScope;
this.functionScope = LinkedFlowScope.createEntryLattice(functionScope);
this.assertionFunctionsMap = assertionFunctionsMap;
for (Var unflowableVar : unflowableVars) {
String name = unflowableVar.getName();
if (functionScope.getVar(name) == unflowableVar) {
this.unflowableVarNames.add(name);
}
}
Iterator<Var> varIt = functionScope.getVars();
while (varIt.hasNext()) {
Var var = varIt.next();
if (this.unflowableVarNames.contains(var.getName())) {
continue;
}
// For each local variable declared with the VAR keyword, the entry
// type is VOID.
if (var.getParentNode() != null &&
var.getType() == null && // no declared type
var.getParentNode().getType() == Token.VAR &&
!var.isExtern()) {
this.functionScope.infer
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>FlowScope =
traverse(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition, conditionFlowScope, branch == Branch.ON_TRUE);
}
}
break;
}
result.add(newScope.optimize());
}
return result;
}
private FlowScope traverse(Node n, FlowScope scope) {
switch (n.getType()) {
case Token.ASSIGN:
scope = traverseAssign(n, scope);
break;
case Token.NAME:
scope = traverseName(n, scope);
break;
case Token.GETPROP:
scope = traverseGetProp(n, scope);
break;
case Token.AND:
scope = traverseAnd(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.OR:
scope = traverseOr(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.HOOK:
scope = traverseHook(n, scope);
break;
case Token.OBJECTLIT:
scope = traverseObjectLiteral(n, scope);
break;
case Token.CALL:
scope = traverseCall(n, scope);
break;
case Token.NEW:
scope = traverseNew(n, scope);
break;
case Token.ASSIGN_ADD:
case Token.ADD:
scope = traverseAdd(n, scope);
break;
case Token.POS:
case Token.NEG:
scope = traverse(n.getFirstChild(), scope); // Find types.
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.ARRAYLIT:
scope = traverseArrayLiteral(n, scope);
break;
case Token.REF_SPECIAL:
n.setJSType(getNativeType(UNKNOWN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.THIS:
n.setJSType(scope.getType
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
traverseBranch(n.getFirstChild(), n);
traverseBranch(n.getFirstChild().getNext().getNext(), n);
break;
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getType() == Token.FUNCTION);
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.getType() == Token.BLOCK);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode != null);
scopes.push(s);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to the previous scope (e.g. when leaving a function). */
private void popScope() {
if (scopeCallback != null) {
scopeCallback.exitScope(this);
}
if (scopeRoots.isEmpty()) {
scopes.pop();
} else {
scopeRoots.pop();
}
cfgs.pop();
}
/** Gets the current scope. */
public Scope getScope() {
Scope scope = scopes.isEmpty() ? null : scopes.peek();
if (scopeRoots.isEmpty()) {
return scope;
}
Iterator<Node> it = scopeRoots.descendingIterator();
while (it.hasNext()) {
scope = scopeCreator.createScope(it.next(), scope);
scopes.push(scope);
}
scopeRoots.clear();
return scope;
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph<Node> getControlFlowGraph() {
if (cfgs.peek() == null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false);
cfa.process(null, getScopeRoot());
cfgs.pop();
cfgs.push(cfa.getCfg());
}
return
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> }
if (n.getType() == Token.FUNCTION) {
moveNamedFunctions(n.getLastChild());
}
}
// TODO(johnlenz): Move this to NodeTypeNormalizer once the unit tests are
// fixed.
/**
* Limit the number of special cases where LABELs need to be handled. Only
* BLOCK and loops are allowed to be labeled. Loop labels must remain in
* place as the named continues are not allowed for labeled blocks.
*/
private void normalizeLabels(Node n) {
Preconditions.checkArgument(n.getType() == Token.LABEL);
Node last = n.getLastChild();
switch (last.getType()) {
case Token.LABEL:
case Token.BLOCK:
case Token.FOR:
case Token.WHILE:
case Token.DO:
return;
default:
Node block = new Node(Token.BLOCK);
block.copyInformationFrom(last);
n.replaceChild(last, block);
block.addChildToFront(last);
reportCodeChange("LABEL normalization");
return;
}
}
/**
* Bring the initializers out of FOR loops. These need to be placed
* before any associated LABEL nodes. This needs to be done from the top
* level label first so this is called as a pre-order callback (from
* shouldTraverse).
*
* @param n The node to inspect.
* @param before The node to insert the initializer before.
* @param beforeParent The parent of the node before which the initializer
* will be inserted.
*/
private void extractForInitializer(
Node n, Node before, Node beforeParent) {
for (Node next, c = n.getFirstChild(); c != null; c = next) {
next = c.getNext();
Node insertBefore = (before == null) ? c : before;
Node insertBeforeParent = (before == null) ? n : beforeParent;
switch (c.getType()) {
case Token.LABEL:
extractForInitializer(c, insertBefore, insertBeforeParent);
break;
case Token.FOR:
if (!NodeUtil.isForIn(c)
&& c.getFirstChild().getType() != Token.EMPTY) {
Node init = c.getFirstChild();
Node empty = new Node(Token.EMPTY);
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> empty.copyInformationFrom(c);
c.replaceChild(init, empty);
Node newStatement;
// Only VAR statements, and expressions are allowed,
// but are handled differently.
if (init.getType() == Token.VAR) {
newStatement = init;
} else {
newStatement = NodeUtil.newExpr(init);
}
insertBeforeParent.addChildBefore(newStatement, insertBefore);
reportCodeChange("FOR initializer");
}
break;
}
}
}
/**
* Split a var node such as:
* var a, b;
* into individual statements:
* var a;
* var b;
* @param n The whose children we should inspect.
*/
private void splitVarDeclarations(Node n) {
for (Node next, c = n.getFirstChild(); c != null; c = next) {
next = c.getNext();
if (c.getType() == Token.VAR) {
if (assertOnChange && !c.hasChildren()) {
throw new IllegalStateException("Empty VAR node.");
}
while (c.getFirstChild() != c.getLastChild()) {
Node name = c.getFirstChild();
c.removeChild(name);
Node newVar = new Node(
Token.VAR, name, n.getLineno(), n.getCharno());
n.addChildBefore(newVar, c);
reportCodeChange("VAR with multiple children");
}
}
}
}
/**
* Move all the functions that are valid at the execution of the first
* statement of the function to the beginning of the function definition.
*/
private void moveNamedFunctions(Node functionBody) {
Preconditions.checkState(
functionBody.getParent().getType() == Token.FUNCTION);
Node previous = null;
Node current = functionBody.getFirstChild();
// Skip any declarations at the beginning of the function body, they
// are already in the right place.
while (current != null && NodeUtil.isFunctionDeclaration(current)) {
previous = current;
current = current.getNext();
}
// Find any remaining declarations and move them.
Node insertAfter = previous;
while (current != null) {
// Save off the next node as the current node maybe removed.
Node next = current.getNext();
if (
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>NodeUtil.isFunctionDeclaration(current)) {
// Remove the declaration from the body.
Preconditions.checkNotNull(previous);
functionBody.removeChildAfter(previous);
// Readd the function at the top of the function body (after any
// previous declarations).
insertAfter = addToFront(functionBody, current, insertAfter);
reportCodeChange("Move function declaration not at top of function");
} else {
// Update the previous only if the current node hasn't been moved.
previous = current;
}
current = next;
}
}
/**
* @param after The child node to insert the newChild after, or null if
* newChild should be added to the front of parent's child list.
* @return The inserted child node.
*/
private Node addToFront(Node parent, Node newChild, Node after) {
if (after == null) {
parent.addChildToFront(newChild);
} else {
parent.addChildAfter(newChild, after);
}
return newChild;
}
}
/**
* Remove duplicate VAR declarations.
*/
private void removeDuplicateDeclarations(Node root) {
Callback tickler = new ScopeTicklingCallback();
ScopeCreator scopeCreator = new SyntacticScopeCreator(
compiler, new DuplicateDeclarationHandler());
NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator);
t.traverse(root);
}
/**
* ScopeCreator duplicate declaration handler.
*/
private final class DuplicateDeclarationHandler implements
SyntacticScopeCreator.RedeclarationHandler {
/**
* Remove duplicate VAR declarations encountered discovered during
* scope creation.
*/
@Override
public void onRedeclaration(
Scope s, String name, Node n, Node parent, Node gramps,
Node nodeWithLineNumber) {
Preconditions.checkState(n.getType() == Token.NAME);
Var v = s.getVar(name);
// If name is "arguments", Var maybe null.
if (v != null && v.getParentNode().getType() == Token.CATCH) {
// Redeclaration of a catch expression variable is hard to model
// without support for "with" expressions.
// The EcmaScript spec (section 12.14), declares that a catch
// "catch (e) {}" is handled like "with ({'e
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> = it.next();
if (var.isTypeInferred()) {
JSType type = var.getType();
if (type == null || type.isUnknownType()) {
JSType flowType = getSlot(var.getName()).getType();
var.setType(flowType);
}
}
}
}
/**
* Remove flow scopes that add nothing to the flow.
*/
// NOTE(nicksantos): This function breaks findUniqueRefinedSlot, because
// findUniqueRefinedSlot assumes that this scope is a direct descendant
// of blindScope. This is not necessarily true if this scope has been
// optimize()d and blindScope has not. This should be fixed. For now,
// we only use optimize() where we know that we won't have to do
// a findUniqueRefinedSlot on it.
@Override
public LinkedFlowScope optimize() {
LinkedFlowScope current;
for (current = this;
current.parent != null &&
current.lastSlot == current.parent.lastSlot;
current = current.parent) {}
return current;
}
/** Join the two FlowScopes. */
static class FlowScopeJoinOp extends JoinOp.BinaryJoinOp<FlowScope> {
@SuppressWarnings("unchecked")
@Override
public FlowScope apply(FlowScope a, FlowScope b) {
// To join the two scopes, we have to
LinkedFlowScope linkedA = (LinkedFlowScope) a;
LinkedFlowScope linkedB = (LinkedFlowScope) b;
linkedA.frozen = true;
linkedB.frozen = true;
if (linkedA.optimize() == linkedB.optimize()) {
return linkedA.createChildFlowScope();
}
return new LinkedFlowScope(new FlatFlowScopeCache(linkedA, linkedB));
}
}
@Override
public boolean equals(Object other) {
if (other instanceof LinkedFlowScope) {
LinkedFlowScope that = (LinkedFlowScope) other;
if (this.optimize() == that.optimize()) {
return true;
}
// If two flow scopes are in the same function, then they could have
// two possible function scopes: the real one and the BOTTOM scope.
// If they have different function scopes, we *should* iterate thru all
// the variables in each scope and
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>.size() - 1; i >= 0; i--) {
JSModule m = modulesAtDepth.get(i);
if (dependsOn(m1, m) && dependsOn(m2, m)) {
return m;
}
}
}
return null;
}
/**
* Finds the deepest common dependency of two modules, including the
* modules themselves.
*
* @param m1 A module in this graph
* @param m2 A module in this graph
* @return The deepest common dep of {@code m1} and {@code m2}, or null if
* they have no common dependencies
*/
public JSModule getDeepestCommonDependencyInclusive(
JSModule m1, JSModule m2) {
if (m2 == m1 || dependsOn(m2, m1)) {
return m1;
} else if (dependsOn(m1, m2)) {
return m2;
}
return getDeepestCommonDependency(m1, m2);
}
/** Returns the deepest common dependency of the given modules. */
public JSModule getDeepestCommonDependencyInclusive(
Collection<JSModule> modules) {
Iterator<JSModule> iter = modules.iterator();
JSModule dep = iter.next();
while (iter.hasNext()) {
dep = getDeepestCommonDependencyInclusive(dep, iter.next());
}
return dep;
}
/**
* Creates an iterable over the transitive dependencies of module {@code m}
* in a non-increasing depth ordering. The result does not include the module
* {@code m}.
*
* @param m A module in this graph
* @return The transitive dependencies of module {@code m}
*/
Set<JSModule> getTransitiveDepsDeepestFirst(JSModule m) {
Set<JSModule> deps = dependencyMap.get(m);
if (deps != null) {
return deps;
}
deps = new TreeSet<JSModule>(new InverseDepthComparator());
addDeps(deps, m);
dependencyMap.put(m, deps);
return deps;
}
/**
* Adds a module's transitive dependencies to a set.
*/
private void addDeps(Set<JSModule
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>> deps, JSModule m) {
for (JSModule dep : m.getDependencies()) {
deps.add(dep);
addDeps(deps, dep);
}
}
/**
* Replaces any files that are found multiple times with a single instance in
* the closest parent module that is common to all modules where it appears.
*
* JSCompiler normally errors if you attempt to compile modules containing the
* same file. This method can be used to remove duplicates before compiling
* to avoid such an error.
*/
public void coalesceDuplicateFiles() {
Multimap<String, JSModule> fileRefs = LinkedHashMultimap.create();
for (JSModule module : moduleDepths.keySet()) {
for (CompilerInput jsFile : module.getInputs()) {
fileRefs.put(jsFile.getName(), module);
}
}
for (String path : fileRefs.keySet()) {
Collection<JSModule> refModules = fileRefs.get(path);
if (refModules.size() > 1) {
JSModule depModule = getDeepestCommonDependencyInclusive(refModules);
CompilerInput file = refModules.iterator().next().getByName(path);
for (JSModule module : refModules) {
if (module != depModule) {
module.removeByName(path);
}
}
if (!refModules.contains(depModule)) {
depModule.add(file);
}
}
}
}
/**
* Sort the sources of modules in dependency-order.
*
* If a source file provides a symbol that is not required, then that
* file will be removed from the compilation. If a source file provides
* a symbol that is not required until a later module, then that
* file will be moved to the later module.
*
* @param inputs The original list of sources. Used to ensure that the sort
* is stable.
* @return The sorted list of sources.
*/
List<CompilerInput> manageDependencies(List<CompilerInput> inputs)
throws CircularDependencyException {
SortedDependencies<CompilerInput> sorter =
new SortedDependencies<CompilerInput>(inputs);
List<CompilerInput> inputsWithoutProvides =
sorter.getInputsWithoutProvides();
// The order of inputs, sorted independently of modules.
List<
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
/**
* Gets the names of the modules that this module depends on,
* sorted alphabetically.
*/
List<String> getSortedDependencyNames() {
List<String> names = Lists.newArrayList();
for (JSModule module : getDependencies()) {
names.add(module.getName());
}
Collections.sort(names);
return names;
}
/**
* Returns the transitive closure of dependencies starting from the
* dependencies of this module.
*/
public Set<JSModule> getAllDependencies() {
Set<JSModule> allDeps = Sets.newHashSet(deps);
List<JSModule> workList = Lists.newArrayList(deps);
while (workList.size() > 0) {
JSModule module = workList.remove(workList.size() - 1);
for (JSModule dep : module.getDependencies()) {
if (allDeps.add(dep)) {
workList.add(dep);
}
}
}
return allDeps;
}
/** Returns this module and all of its dependencies in one list. */
public Set<JSModule> getThisAndAllDependencies() {
Set<JSModule> deps = getAllDependencies();
deps.add(this);
return deps;
}
/**
* Gets this module's list of source code inputs.
*
* @return A list that may be empty but not null
*/
public List<CompilerInput> getInputs() {
return inputs;
}
/** Returns the input with the given name or null if none. */
public CompilerInput getByName(String name) {
for (CompilerInput input : inputs) {
if (name.equals(input.getName())) {
return input;
}
}
return null;
}
/**
* Removes any input with the given name. Returns whether any were removed.
*/
public boolean removeByName(String name) {
boolean found = false;
Iterator<CompilerInput> iter = inputs.iterator();
while (iter.hasNext()) {
CompilerInput file = iter.next();
if (name.equals(file.getName())) {
iter.remove();
file.setModule(null);
found = true;
}
}
return found;
}
/** Returns the module name (primarily for debugging). */
@Override
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.REF_SPECIAL:
ensureTyped(t, n);
break;
case Token.GET_REF:
ensureTyped(t, n, getJSType(n.getFirstChild()));
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, NUMBER_TYPE);
} else {
typeable = false;
}
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.STRING:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, STRING_TYPE);
} else {
typeable = false;
}
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !NodeUtil.isExpressionNode(parent);
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left,
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.getType() == Token.OBJECTLIT)
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
checkNoTypeCheckSection(n, false);
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes.isOn()) {
compiler.report(
t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.getType() == Token.ASSIGN</code>
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> n) {
Node child = n.getFirstChild();
JSType childType = getJSType(child).restrictByNotNullOrUndefined();
if (!childType.canBeCalled()) {
report(t, n, NOT_CALLABLE, childType.toString());
ensureTyped(t, n);
return;
}
// A couple of types can be called as if they were functions.
// If it is a function type, then validate parameters.
if (childType instanceof FunctionType) {
FunctionType functionType = (FunctionType) childType;
// Non-native constructors should never be called directly.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType()) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
private void visitParameterList(NodeTraversal t, Node call,
FunctionType functionType) {
Iterator<Node> arguments = call.children().iterator();
arguments.next(); // skip the function name
Iterator<Node> parameters = functionType.getParameters().iterator();
int ordinal = 0;
while (arguments.hasNext() && parameters.hasNext()) {
Node parameter = parameters.next();
Node argument = arguments.next();
ordinal++;
validator.expectArgumentMatchesParameter(t, argument,
getJSType(argument), getJSType(parameter), call, ordinal);
}
int numArgs = call.getChildCount() - 1;
int minArgs = functionType.getMinArguments();
int maxArgs = functionType.getMaxArguments();
if (minArgs > numArgs || maxArgs < numArgs) {
report(t, call, WRONG_ARGUMENT_COUNT,
validator.getReadableJSTypeName(call.getFirstChild(), false),
String.valueOf(numArgs), String.valueOf(minArgs),
maxArgs
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> value used for initialization of the enum
* @param primitiveType The type of each element of the enum.
*/
private void checkEnumInitializer(
NodeTraversal t, Node value, JSType primitiveType) {
if (value.getType() == Token.OBJECTLIT) {
// re-using value as the value of the object literal and advancing twice
value = value.getFirstChild();
value = (value == null) ? null : value.getNext();
while (value != null) {
// the value's type must be assignable to the enum's primitive type
validator.expectCanAssignTo(t, value, getJSType(value), primitiveType,
"element type must match enum's type");
// advancing twice
value = value.getNext();
value = (value == null) ? null : value.getNext();
}
} else if (value.getJSType() instanceof EnumType) {
// TODO(user): Remove the instanceof check in favor
// of a type.isEnumType() predicate. Currently, not all enum types are
// implemented by the EnumClass, e.g. the unknown type and the any
// type. The types need to be defined by interfaces such that an
// implementation can implement multiple types interface.
EnumType valueEnumType = (EnumType) value.getJSType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
primitiveType, "incompatible enum element types");
} else {
// The error condition is handled in TypedScopeCreator.
}
}
/**
* This predicate is used to determine if the node represents an expression
* that is a Reference according to JavaScript definitions.
*
* @param n The node being checked.
* @return true if the sub-tree n is a reference, false otherwise.
*/
private static boolean isReference(Node n) {
switch (n.getType()) {
case Token.GETELEM:
case Token.GETPROP:
case Token.NAME:
return true;
default:
return false;
}
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>Info(fileOverviewInfo);
jsdocParser.parse();
return jsdocParser;
}
/** Attach JSDocInfo to a node, if we can find one. */
private void attachJsDoc(Comment comment, JSDocInfo info) {
Collection<NodeWithJsDoc> candidates =
nodesWithJsDoc.get(comment.getValue());
if (candidates.isEmpty()) {
return;
}
Iterator<NodeWithJsDoc> candidateIter = candidates.iterator();
Node node = candidateIter.next().node;
candidateIter.remove();
node.setJSDocInfo(info);
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.jscomp.mozilla.rhino.Node n) {
Node node = newNode(transformTokenType(n.getType()));
for (com.google.javascript.jscomp.mozilla.rhino.Node child : n) {
node.addChildToBack(transform((AstNode)child));
}
return node;
}
/**
* Transforms the given node and then sets its type to Token.STRING if it
* was Token.NAME. If its type was already Token.STRING, then quotes it.
* Used for properties, as the old AST uses String tokens, while the new one
* uses Name tokens for unquoted strings. For example, in
* var o = {'a' : 1, b: 2};
* the string 'a' is quoted, while the name b is turned into a string, but
* unquoted.
*/
private Node transformAsString(AstNode n) {
Node ret = transform(n);
if (ret.getType() == Token.STRING) {
ret.putBooleanProp(Node.QUOTED_PROP, true);
} else if (ret.getType() == Token.NAME) {
ret.setType(Token
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>NumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (el.isGetter()) {
reportGetter(el);
} else if (el.isSetter()) {
reportSetter(el);
} else {
node.addChildToBack(transformAsString(el.getLeft()));
node.addChildToBack(transform(el.getRight()));
}
}
return node;
}
@Override
Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
return newNode(
Token.GETPROP,
transform(getNode.getTarget()),
transformAsString(getNode.getProperty()));
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode = newStringNode(literalNode.getValue());
// assume it's on the same line.
literalStringNode.setLineno(literalNode.getLineno());
Node node = newNode(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !flags.isEmpty()) {
Node flagsNode = newStringNode(flags);
// Assume the flags are on the same line as the literal node.
flagsNode.setLineno(literalNode.getLineno());
node.addChildToBack(flagsNode);
}
return node;
}
@Override
Node processReturnStatement(ReturnStatement statementNode) {
Node node = newNode(Token.RETURN);
if (statementNode.getReturnValue() != null) {
node.addChildToBack(transform(statementNode.getReturnValue()));
}
return node;
}
@Override
Node processScope(Scope scopeNode) {
return
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>.javascript.jscomp.mozilla.rhino.Token.RETHROW:
return Token.RETHROW;
case com.google.javascript.jscomp.mozilla.rhino.Token.IN:
return Token.IN;
case com.google.javascript.jscomp.mozilla.rhino.Token.INSTANCEOF:
return Token.INSTANCEOF;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_LOAD:
return Token.LOCAL_LOAD;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETVAR:
return Token.GETVAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETVAR:
return Token.SETVAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.CATCH_SCOPE:
return Token.CATCH_SCOPE;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_INIT_KEYS:
return Token.ENUM_INIT_KEYS;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_INIT_VALUES:
return Token.ENUM_INIT_VALUES;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_NEXT:
return Token.ENUM_NEXT;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_ID:
return Token.ENUM_ID;
case com.google.javascript.jscomp.mozilla.rhino.Token.THISFN:
return Token.THISFN;
case com.google.javascript.jscomp.mozilla.rhino.Token.RETURN_RESULT:
return Token.RETURN_RESULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.ARRAYLIT:
return Token.ARRAYLIT;
case com.google.javascript.jscomp.mozilla.rhino.Token.OBJECTLIT:
return Token.OBJECTLIT;
case com.google.javascript.jscomp.mozilla.rhino.Token.GET_REF:
return Token.GET_REF;
case com.google.javascript.jscomp.mozilla.rhino.Token.SET_REF:
return Token.SET_REF;
case com.google.javascript.jscomp.mozilla.rh
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> Sets.<INPUT>newHashSet());
}
private List<INPUT> findCycle(
INPUT current, Set<INPUT> subGraph, Multimap<INPUT, INPUT> deps,
Set<INPUT> covered) {
if (covered.add(current)) {
List<INPUT> cycle = findCycle(
findRequireInSubGraphOrFail(current, subGraph),
subGraph, deps, covered);
// Don't add the input to the list if the cycle has closed already.
if (cycle.get(0) != cycle.get(cycle.size() - 1)) {
cycle.add(current);
}
return cycle;
} else {
// Explicitly use the add() method, to prevent a generics constructor
// warning that is dumb. The condition it's protecting is
// obscure, and I think people have proposed that it be removed.
List<INPUT> cycle = Lists.<INPUT>newArrayList();
cycle.add(current);
return cycle;
}
}
private INPUT findRequireInSubGraphOrFail(INPUT input, Set<INPUT> subGraph) {
for (String symbol : input.getRequires()) {
INPUT candidate = provideMap.get(symbol);
if (subGraph.contains(candidate)) {
return candidate;
}
}
throw new IllegalStateException("no require found in subgraph");
}
/**
* @param cycle A cycle in reverse-dependency order.
*/
private String cycleToString(List<INPUT> cycle) {
List<String> symbols = Lists.newArrayList();
for (int i = cycle.size() - 1; i >= 0; i--) {
symbols.add(cycle.get(i).getProvides().iterator().next());
}
symbols.add(symbols.get(0));
return Joiner.on(" -> ").join(symbols);
}
public List<INPUT> getSortedList() {
return Collections.<INPUT>unmodifiableList(sortedList);
}
/**
* Gets all the dependencies of the given roots. The inputs must be returned
* in a stable order. In other words, if A comes before B, and A does not
* transitively depend on B, then A must also come before B in the returned
* list.
*/
public List<INPUT> getSortedDependenciesOf(List<INPUT>
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
public Scope createScope(Node n, Scope parent) {
sourceName = null;
if (parent == null) {
scope = new Scope(n, compiler);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
sourceName = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
if (n.getType() == Token.FUNCTION) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnName, fnNameNode, n, null, null, n);
}
// Args: Declare function variables
Preconditions.checkState(args.getType() == Token.LP);
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.getType() == Token.NAME);
declareVar(a.getString(), a, args, n, null, n);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
Preconditions.checkState(child.getType() == Token.NAME);
String name = child.getString
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>();
declareVar(name, child, n, parent, null, n);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(fnName, n.getFirstChild(), n, parent, null, n);
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext().getNext();
declareVar(var.getString(), var, n, parent, null, n);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name,
Node n, Node parent, Node gramps, Node nodeWithLineNumber);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
public void onRedeclaration(
Scope s, String name,
Node n, Node parent, Node gramps, Node nodeWithLineNumber) {
// Don't allow multiple variables to be declared at the top level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>.<L>newArrayList(latticeA, latticeB));
}
/**
* Checks whether the analysis is a forward flow analysis or backward flow
* analysis.
*
* @return {@code true} if it is a forward analysis.
*/
abstract boolean isForward();
/**
* Computes the output state for a given node and input state.
*
* @param node The node.
* @param input Input lattice that should be read-only.
* @return Output lattice.
*/
abstract L flowThrough(N node, L input);
/**
* Finds a fixed-point solution using at most {@link #MAX_STEPS}
* iterations.
*
* @see #analyze(int)
*/
final void analyze() {
analyze(MAX_STEPS);
}
/**
* Finds a fixed-point solution. The function has the side effect of replacing
* the existing node annotations with the computed solutions using {@link
* com.google.javascript.jscomp.graph.GraphNode#setAnnotation(Annotation)}.
*
* <p>Initially, each node's input and output flow state contains the value
* given by {@link #createInitialEstimateLattice()} (with the exception of the
* entry node of the graph which takes on the {@link #createEntryLattice()}
* value. Each node will use the output state of its predecessor and compute a
* output state according to the instruction. At that time, any nodes that
* depends on the node's newly modified output value will need to recompute
* their output state again. Each step will perform a computation at one node
* until no extra computation will modify any existing output state anymore.
*
* @param maxSteps Max number of iterations before the method stops and throw
* a {@link MaxIterationsExceededException}. This will prevent the
* analysis from going into a infinite loop.
*/
final void analyze(int maxSteps) {
initialize();
int step = 0;
while (!orderedWorkSet.isEmpty()) {
if (step > maxSteps) {
throw new MaxIterationsExceededException(
"Analysis did not terminate after " + maxSteps + " iterations");
}
DiGraphNode<N, Branch> curNode = orderedWorkSet.iterator().next();
orderedWorkSet.remove(curNode);
joinInputs(cur
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
* variable local not to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates.
* </li>
* <li>Names of named functions because in javascript, <i>function foo(){}</i>
* does not kill <i>foo</i> in the dataflow.</li>
*/
static void computeEscaped(final Scope jsScope, final Set<Var> escaped,
AbstractCompiler compiler) {
// TODO(user): Very good place to store this information somewhere.
AbstractPostOrderCallback finder = new AbstractPostOrderCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (jsScope == t.getScope() || !NodeUtil.isName(n)
|| NodeUtil.isFunction(parent)) {
return;
}
String name = n.getString();
Var var = t.getScope().getVar(name);
if (var != null && var.scope == jsScope) {
escaped.add(jsScope.getVar(name));
}
}
};
NodeTraversal t = new NodeTraversal(compiler, finder);
t.traverseAtScope(jsScope);
// 1: Remove the exception name in CATCH which technically isn't local to
// begin with.
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var var = i.next();
if (var.getParentNode().getType() == Token.CATCH ||
compiler.getCodingConvention().isExported(var.getName())) {
escaped.add(var);
}
}
}
}
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> {
this(new ContextualRenamer());
}
MakeDeclaredNamesUnique(Renamer renamer) {
this.rootRenamer = renamer;
}
static CompilerPass getContextualRenameInverter(AbstractCompiler compiler) {
return new ContextualRenameInverter(compiler);
}
@Override
public void enterScope(NodeTraversal t) {
Node declarationRoot = t.getScopeRoot();
Renamer renamer;
if (nameStack.isEmpty()) {
// If the contextual renamer is being used the starting context can not
// be a function.
Preconditions.checkState(
declarationRoot.getType() != Token.FUNCTION ||
!(rootRenamer instanceof ContextualRenamer));
Preconditions.checkState(t.inGlobalScope());
renamer = rootRenamer;
} else {
renamer = nameStack.peek().forChildScope();
}
if (declarationRoot.getType() == Token.FUNCTION) {
// Add the function parameters
Node fnParams = declarationRoot.getFirstChild().getNext();
for (Node c = fnParams.getFirstChild(); c != null; c = c.getNext()) {
String name = c.getString();
renamer.addDeclaredName(name);
}
// Add the function body declarations
Node functionBody = declarationRoot.getLastChild();
findDeclaredNames(functionBody, null, renamer);
} else {
// Add the block declarations
findDeclaredNames(declarationRoot, null, renamer);
}
nameStack.push(renamer);
}
@Override
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
nameStack.pop();
}
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.FUNCTION:
{
// Add recursive function name, if needed.
// NOTE: "enterScope" is called after we need to pick up this name.
Renamer renamer = nameStack.peek().forChildScope();
// If needed, add the function recursive name.
String name = n.getFirstChild().getString();
if (name != null && !name.isEmpty() && parent != null
&& !NodeUtil.isFunctionDeclaration(n
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>Util.isFunctionDeclaration(n)) {
Node nameNode = n.getFirstChild();
renamer.addDeclaredName(nameNode.getString());
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
findDeclaredNames(c, n, renamer);
}
}
}
/**
* Declared names renaming policy interface.
*/
interface Renamer {
/**
* Called when a declared name is found in the local current scope.
*/
void addDeclaredName(String name);
/**
* @return A replacement name, null if oldName is unknown or should not
* be replaced.
*/
String getReplacementName(String oldName);
/**
* @return Whether the constant-ness of a name should be removed.
*/
boolean stripConstIfReplaced();
/**
* @return A Renamer for a scope within the scope of the current Renamer.
*/
Renamer forChildScope();
}
/**
* Inverts the transformation by {@link ContextualRenamer}, when possible.
*/
static class ContextualRenameInverter
implements ScopedCallback, CompilerPass {
private final AbstractCompiler compiler;
// The set of names referenced in the current scope.
private Set<String> referencedNames = ImmutableSet.of();
// Stack reference sets.
private Deque<Set<String>> referenceStack = new ArrayDeque<Set<String>>();
// Name are globally unique initially, so we don't need a per-scope map.
private Map<String, List<Node>> nameMap = Maps.newHashMap();
private ContextualRenameInverter(AbstractCompiler compiler) {
this.compiler = compiler;
}
public void process(Node externs, Node js) {
NodeTraversal.traverse(compiler, js, this);
}
public static String getOrginalName(String name) {
int index = indexOfSeparator(name);
return (index == -1) ? name : name.substring(0, index);
}
private static int indexOfSeparator(String name) {
return name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR);
}
private boolean containsSeparator(String name) {
return name.indexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR) != -1;
}
/**
*
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> Prepare a set for the new scope.
*/
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
referenceStack.push(referencedNames);
referencedNames = Sets.newHashSet();
}
/**
* Rename vars for the current scope, and merge any referenced
* names into the parent scope reference set.
*/
public void exitScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
handleScopeVar(v);
}
// Merge any names that were referenced but not declared in the current
// scope.
Set<String> current = referencedNames;
referencedNames = referenceStack.pop();
// If there isn't anything left in the stack we will be going into the
// global scope: don't try to build a set of referenced names for the
// global scope.
if (!referenceStack.isEmpty()) {
referencedNames.addAll(current);
}
}
/**
* For the Var declared in the current scope determine if it is possible
* to revert the name to its orginal form without conflicting with other
* values.
*/
void handleScopeVar(Var v) {
String name = v.getName();
if (containsSeparator(name) && !getOrginalName(name).isEmpty()) {
String newName = findReplacementName(name);
referencedNames.remove(name);
// Adding a reference to the new name to prevent either the parent
// scopes or the current scope renaming another var to this new name.
referencedNames.add(newName);
List<Node> references = nameMap.get(name);
Preconditions.checkState(references != null);
for (Node n : references) {
Preconditions.checkState(n.getType() == Token.NAME);
n.setString(newName);
}
compiler.reportCodeChange();
nameMap.remove(name);
}
}
/**
* Find a name usable in the local scope.
*/
private String findReplacementName(String name) {
String original = getOrginalName(name);
String newName = original;
int i = 0;
while (!
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> } else if (NodeUtil.isFunctionDeclaration(n) ||
parent.getType() == Token.NAME) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isConstructor()) {
return ((FunctionType) type).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDe
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> parser = new JsDocInfoParser(
new JsDocTokenStream(typeString),
"typeparsing",
config,
NullErrorReporter.forNewRhino());
return parser.parseTopLevelTypeExpression(parser.next());
}
/**
* Parses a {@link JSDocInfo} object. This parsing method reads all tokens
* returned by the {@link JsDocTokenStream#getJsDocToken()} method until the
* {@link JsDocToken#EOC} is returned.
*
* @return {@code true} if JSDoc information was correctly parsed,
* {@code false} otherwise
*/
boolean parse() {
int lineno;
int charno;
// JSTypes are represented as Rhino AST nodes, and then resolved later.
JSTypeExpression type;
state = State.SEARCHING_ANNOTATION;
JsDocToken token = next();
ExtractionInfo blockInfo = extractBlockComment(token);
token = blockInfo.token;
// If we have a block level comment, record it.
if (blockInfo.string.length() > 0) {
jsdocBuilder.recordBlockDescription(blockInfo.string);
}
// Parse the actual JsDoc.
retry: for (;;) {
switch (token) {
case ANNOTATION:
if (state == State.SEARCHING_ANNOTATION) {
state = State.SEARCHING_NEWLINE;
lineno = stream.getLineno();
charno = stream.getCharno();
String annotationName = stream.getString();
Annotation annotation = annotationNames.get(annotationName);
if (annotation == null) {
parser.addWarning("msg.bad.jsdoc.tag", annotationName,
stream.getLineno(), stream.getCharno());
} else {
// Mark the beginning of the annotation.
jsdocBuilder.markAnnotation(annotationName, lineno, charno);
switch (annotation) {
case AUTHOR:
ExtractionInfo authorInfo = extractSingleLineBlock();
String author = authorInfo.string;
if (author.length() == 0) {
parser.addWarning("msg.jsdoc.authormissing",
stream.getLineno(), stream.getCharno());
} else {
jsdocBuilder.addAuthor(author);
}
token
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> case FILE_OVERVIEW:
ExtractionInfo fileOverviewInfo =
extractMultilineTextualBlock(token,
WhitespaceOption.TRIM);
String fileOverview = fileOverviewInfo.string;
if (!jsdocBuilder.recordFileOverview(fileOverview) ||
fileOverviewJSDocInfo != null) {
parser.addWarning("msg.jsdoc.fileoverview.extra",
stream.getLineno(), stream.getCharno());
}
token = fileOverviewInfo.token;
continue retry;
case LICENSE:
case PRESERVE:
ExtractionInfo preserveInfo =
extractMultilineTextualBlock(token,
WhitespaceOption.PRESERVE);
String preserve = preserveInfo.string;
if (preserve.length() > 0) {
if (fileLevelJsDocBuilder != null) {
fileLevelJsDocBuilder.append(preserve);
}
}
token = preserveInfo.token;
continue retry;
case ENUM:
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
type = null;
if (token != JsDocToken.EOL && token != JsDocToken.EOC) {
type = createJSTypeExpression(
parseAndRecordTypeNode(token));
}
if (type == null) {
type = createJSTypeExpression(newStringNode("number"));
}
if (!jsdocBuilder.recordEnumParameterType(type)) {
parser.addWarning("msg.jsdoc.incompat.type", lineno, charno);
}
token = eatTokensUntilEOL(token);
continue retry;
case EXPORT:
if (!jsdocBuilder.recordExport()) {
parser.addWarning("msg.jsdoc.export",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case EXTERNS:
if (!jsdocBuilder.recordExterns()) {
parser.addWarning("msg.jsdoc.externs",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case JAVA_DISPATCH:
if (!jsdocBuilder.recordJavaDispatch()) {
parser.addWarning("msg.jsdoc.javadispatch",
stream.
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case EXTENDS:
case IMPLEMENTS:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
boolean matchingRc = false;
if (token == JsDocToken.LC) {
token = next();
matchingRc = true;
}
if (token == JsDocToken.STRING) {
Node typeNode = parseAndRecordTypeNameNode(
token, lineno, charno, matchingRc);
lineno = stream.getLineno();
charno = stream.getCharno();
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && !matchingRc) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
type = createJSTypeExpression(typeNode);
if (annotation == Annotation.EXTENDS) {
if (!jsdocBuilder.recordBaseType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
} else {
Preconditions.checkState(
annotation == Annotation.IMPLEMENTS);
if (!jsdocBuilder.recordImplementedInterface(type)) {
parser.addWarning("msg.jsdoc.implements.duplicate",
lineno, charno);
}
}
token = next();
if (matchingRc) {
if (token != JsDocToken.RC) {
parser.addWarning("msg.jsdoc.missing.rc",
stream.getLineno(), stream.getCharno());
}
} else if (token != JsDocToken.EOL &&
token != JsDocToken.EOF && token != JsDocToken.EOC) {
parser.addWarning("msg.end.annotation.expected",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addWarning("msg.no.type.name", lineno, charno);
}
token = eatTokensUntilEOL(token);
continue retry;
case HIDDEN:
if (!jsdocBuilder.recordHiddenness()) {
parser.addWarning("msg.jsdoc.hidden",
stream.getLineno(), stream.get
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>Charno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_ALIAS:
if (!jsdocBuilder.recordNoAlias()) {
parser.addWarning("msg.jsdoc.noalias",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_COMPILE:
if (!jsdocBuilder.recordNoCompile()) {
parser.addWarning("msg.jsdoc.nocompile",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_TYPE_CHECK:
if (!jsdocBuilder.recordNoTypeCheck()) {
parser.addWarning("msg.jsdoc.nocheck",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NOT_IMPLEMENTED:
token = eatTokensUntilEOL();
continue retry;
case INHERIT_DOC:
case OVERRIDE:
if (!jsdocBuilder.recordOverride()) {
parser.addWarning("msg.jsdoc.override",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case THROWS:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
type = null;
if (token == JsDocToken.LC) {
type = createJSTypeExpression(
parseAndRecordTypeNode(token));
if (type == null) {
// parsing error reported during recursive descent
// recovering parsing
token = eatTokensUntilEOL();
continue retry;
}
}
// *Update* the token to that after the type annotation.
token = current();
// Save the throw type.
jsdocBuilder.recordThrowType(type);
// Find the throw's description (if applicable).
ExtractionInfo descriptionInfo =
extractMultilineTextualBlock(token);
String description = descriptionInfo.string;
if (description.length() > 0) {
jsdocBuilder.recordThrowDescription(type, description);
}
token = descriptionInfo.token;
continue retry;
case PARAM:
skipEOLs();
token
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> = next();
lineno = stream.getLineno();
charno = stream.getCharno();
type = null;
if (token == JsDocToken.LC) {
type = createJSTypeExpression(
parseAndRecordParamTypeNode(token));
if (type == null) {
// parsing error reported during recursive descent
// recovering parsing
token = eatTokensUntilEOL();
continue retry;
}
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
}
String name = null;
boolean isBracketedParam = JsDocToken.LB == token;
if (isBracketedParam) {
token = next();
}
if (JsDocToken.STRING != token) {
parser.addWarning("msg.missing.variable.name",
lineno, charno);
} else {
name = stream.getString();
if (isBracketedParam) {
token = next();
// Throw out JsDocToolkit's "default" parameter annotation.
// It makes no sense under our type system.
if (JsDocToken.EQUALS == token) {
token = next();
if (JsDocToken.STRING == token) {
token = next();
}
}
if (JsDocToken.RB != token) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
} else if (type != null) {
// Make the type expression optional, if it isn't
// already.
type = JSTypeExpression.makeOptionalArg(type);
}
}
// If the param name has a DOT in it, just throw it out
// quietly. We do not handle the JsDocToolkit method
// for handling properties of params.
if (name.indexOf('.') > -1) {
name = null;
} else if (!jsdocBuilder.recordParameter(name, type)) {
if (jsdocBuilder.hasParameter(name)) {
parser.addWarning("msg.dup.variable.name", name,
lineno, charno);
} else {
parser.addWarning("msg.jsdoc.incompat.type", name,
lineno, charno);
}
}
}
if (name == null) {
token = eatTokensUntilEOL(
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>implicitcast",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case SEE:
ExtractionInfo referenceInfo = extractSingleLineBlock();
String reference = referenceInfo.string;
if (reference.length() == 0) {
parser.addWarning("msg.jsdoc.seemissing",
stream.getLineno(), stream.getCharno());
} else {
jsdocBuilder.addReference(reference);
}
token = referenceInfo.token;
continue retry;
case SUPPRESS:
token = parseSuppressTag(next());
continue retry;
case TEMPLATE:
ExtractionInfo templateInfo = extractSingleLineBlock();
String templateTypeName = templateInfo.string;
if (templateTypeName.length() == 0) {
parser.addWarning("msg.jsdoc.templatemissing",
stream.getLineno(), stream.getCharno());
} else if (!jsdocBuilder.recordTemplateTypeName(
templateTypeName)) {
parser.addWarning("msg.jsdoc.template.at.most.once",
stream.getLineno(), stream.getCharno());
}
token = templateInfo.token;
continue retry;
case VERSION:
ExtractionInfo versionInfo = extractSingleLineBlock();
String version = versionInfo.string;
if (version.length() == 0) {
parser.addWarning("msg.jsdoc.versionmissing",
stream.getLineno(), stream.getCharno());
} else {
if (!jsdocBuilder.recordVersion(version)) {
parser.addWarning("msg.jsdoc.extraversion",
stream.getLineno(), stream.getCharno());
}
}
token = versionInfo.token;
continue retry;
case DEFINE:
case RETURN:
case THIS:
case TYPE:
case TYPEDEF:
skipEOLs();
lineno = stream.getLineno();
charno = stream.getCharno();
token = next();
Node typeNode = parseAndRecordTypeNode(token, lineno, charno);
if (annotation == Annotation.THIS) {
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && token != JsDocToken.LC) {
typeNode
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>SEARCHING_NEWLINE) {
state = State.SEARCHING_ANNOTATION;
}
token = next();
continue retry;
default:
if (token == JsDocToken.STAR && state == State.SEARCHING_ANNOTATION) {
token = next();
continue retry;
} else {
state = State.SEARCHING_NEWLINE;
token = eatTokensUntilEOL();
continue retry;
}
}
// next token
token = next();
}
}
/**
* Parse a {@code @suppress} tag of the form
* {@code @suppress{warning1|warning2}}.
*
* @param token The current token.
*/
private JsDocToken parseSuppressTag(JsDocToken token) {
if (token == JsDocToken.LC) {
Set<String> suppressions = new HashSet<String>();
while (true) {
if (match(JsDocToken.STRING)) {
String name = stream.getString();
if (!suppressionNames.contains(name)) {
parser.addWarning("msg.jsdoc.suppress.unknown", name,
stream.getLineno(), stream.getCharno());
}
suppressions.add(stream.getString());
token = next();
} else {
parser.addWarning("msg.jsdoc.suppress",
stream.getLineno(), stream.getCharno());
return token;
}
if (match(JsDocToken.PIPE)) {
token = next();
} else {
break;
}
}
if (!match(JsDocToken.RC)) {
parser.addWarning("msg.jsdoc.suppress",
stream.getLineno(), stream.getCharno());
} else {
token = next();
if (!jsdocBuilder.recordSuppressions(suppressions)) {
parser.addWarning("msg.jsdoc.suppress.duplicate",
stream.getLineno(), stream.getCharno());
}
}
}
return token;
}
/**
* Looks for a type expression at the current token and if found,
* returns it. Note that this method consumes input.
*
* @param token The current token.
* @return The type expression found or null if none.
*/
private Node
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> COMMA:
return ",";
case COLON:
return ":";
case GT:
return ">";
case LB:
return "[";
case LC:
return "{";
case LP:
return "(";
case LT:
return ".<";
case QMARK:
return "?";
case PIPE:
return "|";
case RB:
return "]";
case RC:
return "}";
case RP:
return ")";
case STAR:
return "*";
case ELLIPSIS:
return "...";
case EQUALS:
return "=";
case STRING:
return stream.getString();
default:
throw new IllegalStateException(token.toString());
}
}
/**
* Constructs a new {@code JSTypeExpression}.
* @param n A node. May be null.
*/
private JSTypeExpression createJSTypeExpression(Node n) {
return n == null ? null :
new JSTypeExpression(n, sourceName);
}
/**
* Tuple for returning both the string extracted and the
* new token following a call to any of the extract*Block
* methods.
*/
private static class ExtractionInfo {
private final String string;
private final JsDocToken token;
public ExtractionInfo(String string, JsDocToken token) {
this.string = string;
this.token = token;
}
}
/**
* Extracts the text found on the current line starting at token. Note that
* token = token.info; should be called after this method is used to update
* the token properly in the parser.
*
* @return The extraction information.
*/
private ExtractionInfo extractSingleLineBlock() {
// Get the current starting point.
stream.update();
int lineno = stream.getLineno();
int charno = stream.getCharno() + 1;
String line = stream.getRemainingJSDocLine().trim();
// Record the textual description.
if (line.length() > 0) {
jsdocBuilder.markText(line, lineno, charno, lineno,
charno + line.length());
}
return new ExtractionInfo(line, next());
}
private ExtractionInfo extractMultilineTextualBlock(JsDocToken token) {
return extractMultilineTextual
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>Block(token, WhitespaceOption.SINGLE_LINE);
}
private enum WhitespaceOption {
/**
* Preserves all whitespace and formatting. Needed for licenses and
* purposely formatted text.
*/
PRESERVE,
/** Preserves newlines but trims the output. */
TRIM,
/** Removes newlines and turns the output into a single line string. */
SINGLE_LINE
}
/**
* Extracts the text found on the current line and all subsequent
* until either an annotation, end of comment or end of file is reached.
* Note that if this method detects an end of line as the first token, it
* will quit immediately (indicating that there is no text where it was
* expected). Note that token = info.token; should be called after this
* method is used to update the token properly in the parser.
*
* @param token The start token.
* @param option How to handle whitespace.
*
* @return The extraction information.
*/
@SuppressWarnings("fallthrough")
private ExtractionInfo extractMultilineTextualBlock(JsDocToken token,
WhitespaceOption option) {
if (token == JsDocToken.EOC || token == JsDocToken.EOL ||
token == JsDocToken.EOF) {
return new ExtractionInfo("", token);
}
stream.update();
int startLineno = stream.getLineno();
int startCharno = stream.getCharno() + 1;
// Read the content from the first line.
String line = stream.getRemainingJSDocLine();
if (option != WhitespaceOption.PRESERVE) {
line = line.trim();
}
StringBuilder builder = new StringBuilder();
builder.append(line);
state = State.SEARCHING_ANNOTATION;
token = next();
boolean ignoreStar = false;
do {
switch (token) {
case STAR:
if (!ignoreStar) {
if (builder.length() > 0) {
builder.append(' ');
}
builder.append('*');
}
token = next();
continue;
case EOL:
if (option != WhitespaceOption.SINGLE_LINE) {
builder.append("\n");
}
ignoreStar = true;
token = next();
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
continue;
case ANNOTATION:
case EOC:
case EOF:
// When we're capturing a license block, annotations
// in the block are ok.
if (!(option == WhitespaceOption.PRESERVE &&
token == JsDocToken.ANNOTATION)) {
String multilineText = builder.toString();
if (option != WhitespaceOption.PRESERVE) {
multilineText = multilineText.trim();
}
int endLineno = stream.getLineno();
int endCharno = stream.getCharno();
if (multilineText.length() > 0) {
jsdocBuilder.markText(multilineText, startLineno, startCharno,
endLineno, endCharno);
}
return new ExtractionInfo(multilineText, token);
}
// FALL THROUGH
default:
ignoreStar = false;
state = State.SEARCHING_ANNOTATION;
if (builder.length() > 0) {
builder.append(' ');
}
builder.append(toString(token));
line = stream.getRemainingJSDocLine();
if (option != WhitespaceOption.PRESERVE) {
line = trimEnd(line);
}
builder.append(line);
token = next();
}
} while (true);
}
/**
* Extracts the top-level block comment from the JsDoc comment, if any.
* This method differs from the extractMultilineTextualBlock in that it
* terminates under different conditions (it doesn't have the same
* prechecks), it does not first read in the remaining of the current
* line and its conditions for ignoring the "*" (STAR) are different.
*
* @param token The starting token.
*
* @return The extraction information.
*/
private ExtractionInfo extractBlockComment(JsDocToken token) {
StringBuilder builder = new StringBuilder();
boolean ignoreStar = true;
do {
switch (token) {
case ANNOTATION:
case EOC:
case EOF:
return new ExtractionInfo(builder.toString().trim(), token);
case STAR:
if (!ignoreStar) {
if (builder.length() > 0) {
builder.append(' ');
}
builder.append('*');
}
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> token = next();
continue;
case EOL:
ignoreStar = true;
builder.append('\n');
token = next();
continue;
default:
if (!ignoreStar && builder.length() > 0) {
builder.append(' ');
}
ignoreStar = false;
builder.append(toString(token));
String line = stream.getRemainingJSDocLine();
line = trimEnd(line);
builder.append(line);
token = next();
}
} while (true);
}
/**
* Trim characters from only the end of a string.
* This method will remove all whitespace characters
* (defined by Character.isWhitespace(char), in addition to the characters
* provided, from the end of the provided string.
*
* @param s String to be trimmed
* @return String with whitespace and characters in extraChars removed
* from the end.
*/
private static String trimEnd(String s) {
int trimCount = 0;
while (trimCount < s.length()) {
char ch = s.charAt(s.length() - trimCount - 1);
if (Character.isWhitespace(ch)) {
trimCount++;
} else {
break;
}
}
if (trimCount == 0) {
return s;
}
return s.substring(0, s.length() - trimCount);
}
// Based on ES4 grammar proposed on July 10, 2008.
// http://wiki.ecmascript.org/doku.php?id=spec:spec
// Deliberately written to line up with the actual grammar rules,
// for maximum flexibility.
// TODO(nicksantos): The current implementation tries to maintain backwards
// compatibility with previous versions of the spec whenever we can.
// We should try to gradually withdraw support for these.
/**
* TypeExpressionAnnotation := TypeExpression |
* '{' TopLevelTypeExpression '}'
*/
private Node parseTypeExpressionAnnotation(JsDocToken token) {
if (token == JsDocToken.LC) {
skipEOLs();
Node typeNode = parseTopLevelTypeExpression(next());
if (typeNode != null) {
skipEOLs();
if (!match(JsDocToken.RC)) {
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
} else {
return parseTypeExpression(token);
}
}
/**
* ParamTypeExpressionAnnotation :=
* '{' OptionalParameterType '}' |
* '{' TopLevelTypeExpression '}' |
* '{' '...' TopLevelTypeExpression '}'
*
* OptionalParameterType :=
* TopLevelTypeExpression '='
*/
private Node parseParamTypeExpressionAnnotation(JsDocToken token) {
Preconditions.checkArgument(token == JsDocToken.LC);
skipEOLs();
boolean restArg = false;
token = next();
if (token == JsDocToken.ELLIPSIS) {
token = next();
if (token == JsDocToken.RC) {
// EMPTY represents the UNKNOWN type in the Type AST.
return wrapNode(Token.ELLIPSIS, new Node(Token.EMPTY));
}
restArg = true;
}
Node typeNode = parseTopLevelTypeExpression(token);
if (typeNode != null) {
skipEOLs();
if (restArg) {
typeNode = wrapNode(Token.ELLIPSIS, typeNode);
} else if (match(JsDocToken.EQUALS)) {
next();
skipEOLs();
typeNode = wrapNode(Token.EQUALS, typeNode);
}
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
}
/**
* TypeNameAnnotation := TypeName | '{' TypeName '}'
*/
private Node parseTypeNameAnnotation(JsDocToken token) {
if (token == JsDocToken.LC) {
skipEOLs();
Node typeNode = parseTypeName(next());
if (typeNode != null) {
skipEOLs();
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
} else {
return parseTypeName(token);
}
}
/**
* TopLevelTypeExpression := TypeExpression
* | Type
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>UnionList
*
* We made this rule up, for the sake of backwards compatibility.
*/
private Node parseTopLevelTypeExpression(JsDocToken token) {
Node typeExpr = parseTypeExpression(token);
if (typeExpr != null) {
// top-level unions are allowed
if (match(JsDocToken.PIPE)) {
next();
if (match(JsDocToken.PIPE)) {
// We support double pipes for backwards-compatibility.
next();
}
skipEOLs();
token = next();
return parseUnionTypeWithAlternate(token, typeExpr);
}
}
return typeExpr;
}
/**
* TypeExpressionList := TopLevelTypeExpression
* | TopLevelTypeExpression ',' TypeExpressionList
*/
private Node parseTypeExpressionList(JsDocToken token) {
Node typeExpr = parseTopLevelTypeExpression(token);
if (typeExpr == null) {
return null;
}
Node typeList = new Node(Token.BLOCK);
typeList.addChildToBack(typeExpr);
while (match(JsDocToken.COMMA)) {
next();
skipEOLs();
typeExpr = parseTopLevelTypeExpression(next());
if (typeExpr == null) {
return null;
}
typeList.addChildToBack(typeExpr);
}
return typeList;
}
/**
* TypeExpression := BasicTypeExpression
* | '?' BasicTypeExpression
* | '!' BasicTypeExpression
* | BasicTypeExpression '?'
* | BasicTypeExpression '!'
* | '?'
*/
private Node parseTypeExpression(JsDocToken token) {
if (token == JsDocToken.QMARK) {
// A QMARK could mean that a type is nullable, or that it's unknown.
// We use look-ahead 1 to determine whether it's unknown. Otherwise,
// we assume it means nullable. There are 5 cases:
// {?} - right curly
// {?=} - equals
// {function(?, number)} - comma
// {function(number, ?)} - right paren
// {function(): ?|number} - pipe
// I'm not a big fan of using look-ahead for this, but it makes
// the type language a lot nicer.
token = next
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>();
if (token == JsDocToken.COMMA ||
token == JsDocToken.EQUALS ||
token == JsDocToken.RC ||
token == JsDocToken.RP ||
token == JsDocToken.PIPE) {
restoreLookAhead(token);
return newNode(Token.QMARK);
}
return wrapNode(Token.QMARK, parseBasicTypeExpression(token));
} else if (token == JsDocToken.BANG) {
return wrapNode(Token.BANG, parseBasicTypeExpression(next()));
} else {
Node basicTypeExpr = parseBasicTypeExpression(token);
if (basicTypeExpr != null) {
if (match(JsDocToken.QMARK)) {
next();
return wrapNode(Token.QMARK, basicTypeExpr);
} else if (match(JsDocToken.BANG)) {
next();
return wrapNode(Token.BANG, basicTypeExpr);
}
}
return basicTypeExpr;
}
}
/**
* BasicTypeExpression := '*' | 'null' | 'undefined' | TypeName
* | FunctionType | UnionType | RecordType | ArrayType
*/
private Node parseBasicTypeExpression(JsDocToken token) {
if (token == JsDocToken.STAR) {
return newNode(Token.STAR);
} else if (token == JsDocToken.LB) {
skipEOLs();
return parseArrayType(next());
} else if (token == JsDocToken.LC) {
skipEOLs();
return parseRecordType(next());
} else if (token == JsDocToken.LP) {
skipEOLs();
return parseUnionType(next());
} else if (token == JsDocToken.STRING) {
String string = stream.getString();
if ("function".equals(string)) {
skipEOLs();
return parseFunctionType(next());
} else if ("null".equals(string) || "undefined".equals(string)) {
return newStringNode(string);
} else {
return parseTypeName(token);
}
}
return reportGenericTypeSyntaxWarning();
}
/**
* TypeName := NameExpression | NameExpression TypeApplication
* TypeApplication := '.<' TypeExpressionList '>'
* TypeExpressionList := TypeExpression // a white lie
*/
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> private Node parseTypeName(JsDocToken token) {
if (token != JsDocToken.STRING) {
return reportGenericTypeSyntaxWarning();
}
Node typeName = newStringNode(stream.getString());
if (match(JsDocToken.LT)) {
next();
skipEOLs();
Node memberType = parseTypeExpressionList(next());
if (memberType != null) {
typeName.addChildToFront(memberType);
skipEOLs();
if (!match(JsDocToken.GT)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.gt");
}
next();
}
}
return typeName;
}
/**
* FunctionType := 'function' FunctionSignatureType
* FunctionSignatureType :=
* TypeParameters '(' 'this' ':' TypeName, ParametersType ')' ResultType
*/
private Node parseFunctionType(JsDocToken token) {
// NOTE(nicksantos): We're not implementing generics at the moment, so
// just throw out TypeParameters.
if (token != JsDocToken.LP) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.lp");
}
Node functionType = newNode(Token.FUNCTION);
Node parameters = null;
skipEOLs();
if (!match(JsDocToken.RP)) {
token = next();
boolean hasParams = true;
if (token == JsDocToken.STRING && "this".equals(stream.getString())) {
if (match(JsDocToken.COLON)) {
next();
skipEOLs();
Node thisType = wrapNode(Token.THIS, parseTypeName(next()));
if (thisType == null) {
return null;
}
functionType.addChildToFront(thisType);
} else {
return reportTypeSyntaxWarning("msg.jsdoc.missing.colon");
}
if (match(JsDocToken.COMMA)) {
next();
skipEOLs();
token = next();
} else {
hasParams = false;
}
}
if (hasParams) {
parameters = parseParametersType(token);
if (parameters == null) {
return null;
}
}
}
if (parameters != null) {
functionType.addChildToBack(parameters);
}
skipEOLs();
if (!match(Js
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>DocToken.RP)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rp");
}
skipEOLs();
Node resultType = parseResultType(next());
if (resultType == null) {
return null;
} else {
functionType.addChildToBack(resultType);
}
return functionType;
}
/**
* ParametersType := RestParameterType | NonRestParametersType
* | NonRestParametersType ',' RestParameterType
* RestParameterType := '...' Identifier
* NonRestParametersType := ParameterType ',' NonRestParametersType
* | ParameterType
* | OptionalParametersType
* OptionalParametersType := OptionalParameterType
* | OptionalParameterType, OptionalParametersType
* OptionalParameterType := ParameterType=
* ParameterType := TypeExpression | Identifier ':' TypeExpression
*/
// NOTE(nicksantos): The official ES4 grammar forces optional and rest
// arguments to come after the required arguments. Our parser does not
// enforce this. Instead we allow them anywhere in the function at parse-time,
// and then warn about them during type resolution.
//
// In theory, it might be mathematically nicer to do the order-checking here.
// But in practice, the order-checking for structural functions is exactly
// the same as the order-checking for @param annotations. And the latter
// has to happen during type resolution. Rather than duplicate the
// order-checking in two places, we just do all of it in type resolution.
private Node parseParametersType(JsDocToken token) {
Node paramsType = newNode(Token.LP);
boolean isVarArgs = false;
Node paramType = null;
if (token != JsDocToken.RP) {
do {
if (paramType != null) {
// skip past the comma
next();
skipEOLs();
token = next();
}
if (token == JsDocToken.ELLIPSIS) {
// In the latest ES4 proposal, there are no type constraints allowed
// on variable arguments. We support the old syntax for backwards
// compatibility, but we should gradually tear it out.
skipEOLs();
if (match(JsDocToken.RP)) {
paramType = newNode(Token.ELLIPSIS);
} else {
skipEOLs();
if (!match(
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>JsDocToken.LB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.lb");
}
next();
skipEOLs();
paramType = wrapNode(Token.ELLIPSIS, parseTypeExpression(next()));
skipEOLs();
if (!match(JsDocToken.RB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
}
skipEOLs();
next();
}
isVarArgs = true;
} else {
paramType = parseTypeExpression(token);
if (match(JsDocToken.EQUALS)) {
skipEOLs();
next();
paramType = wrapNode(Token.EQUALS, paramType);
}
}
if (paramType == null) {
return null;
}
paramsType.addChildToBack(paramType);
if (isVarArgs) {
break;
}
} while (match(JsDocToken.COMMA));
}
if (isVarArgs && match(JsDocToken.COMMA)) {
return reportTypeSyntaxWarning("msg.jsdoc.function.varargs");
}
// The right paren will be checked by parseFunctionType
return paramsType;
}
/**
* ResultType := <empty> | ':' void | ':' TypeExpression
*/
private Node parseResultType(JsDocToken token) {
skipEOLs();
if (!match(JsDocToken.COLON)) {
return newNode(Token.EMPTY);
}
token = next();
skipEOLs();
if (match(JsDocToken.STRING) && "void".equals(stream.getString())) {
next();
return newNode(Token.VOID);
} else {
return parseTypeExpression(next());
}
}
/**
* UnionType := '(' TypeUnionList ')'
* TypeUnionList := TypeExpression | TypeExpression '|' TypeUnionList
*
* We've removed the empty union type.
*/
private Node parseUnionType(JsDocToken token) {
return parseUnionTypeWithAlternate(token, null);
}
/**
* Create a new union type, with an alternate that has already been
* parsed. The alternate may be null.
*/
private Node parseUnionTypeWithAlternate(JsDocToken token, Node alternate) {
Node union = newNode(Token.
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>PIPE);
if (alternate != null) {
union.addChildToBack(alternate);
}
Node expr = null;
do {
if (expr != null) {
skipEOLs();
token = next();
Preconditions.checkState(
token == JsDocToken.PIPE || token == JsDocToken.COMMA);
boolean isPipe = token == JsDocToken.PIPE;
if (isPipe && match(JsDocToken.PIPE)) {
// We support double pipes for backwards compatiblity.
next();
}
skipEOLs();
token = next();
}
expr = parseTypeExpression(token);
if (expr == null) {
return null;
}
union.addChildToBack(expr);
// We support commas for backwards compatiblity.
} while (match(JsDocToken.PIPE, JsDocToken.COMMA));
if (alternate == null) {
skipEOLs();
if (!match(JsDocToken.RP)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rp");
}
next();
}
return union;
}
/**
* ArrayType := '[' ElementTypeList ']'
* ElementTypeList := <empty> | TypeExpression | '...' TypeExpression
* | TypeExpression ',' ElementTypeList
*/
private Node parseArrayType(JsDocToken token) {
Node array = newNode(Token.LB);
Node arg = null;
boolean hasVarArgs = false;
do {
if (arg != null) {
next();
skipEOLs();
token = next();
}
if (token == JsDocToken.ELLIPSIS) {
arg = wrapNode(Token.ELLIPSIS, parseTypeExpression(next()));
hasVarArgs = true;
} else {
arg = parseTypeExpression(token);
}
if (arg == null) {
return null;
}
array.addChildToBack(arg);
if (hasVarArgs) {
break;
}
skipEOLs();
} while (match(JsDocToken.COMMA));
if (!match(JsDocToken.RB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
}
next();
return array;
}
/**
* RecordType := '{' FieldTypeList '}'
*/
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
private Node parseRecordType(JsDocToken token) {
Node recordType = newNode(Token.LC);
Node fieldTypeList = parseFieldTypeList(token);
if (fieldTypeList == null) {
return reportGenericTypeSyntaxWarning();
}
skipEOLs();
if (!match(JsDocToken.RC)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
}
next();
recordType.addChildToBack(fieldTypeList);
return recordType;
}
/**
* FieldTypeList := FieldType | FieldType ',' FieldTypeList
*/
private Node parseFieldTypeList(JsDocToken token) {
Node fieldTypeList = newNode(Token.LB);
do {
Node fieldType = parseFieldType(token);
if (fieldType == null) {
return null;
}
fieldTypeList.addChildToBack(fieldType);
skipEOLs();
if (!match(JsDocToken.COMMA)) {
break;
}
// Move to the comma token.
next();
// Move to the token passed the comma.
skipEOLs();
token = next();
} while (true);
return fieldTypeList;
}
/**
* FieldType := FieldName | FieldName ':' TypeExpression
*/
private Node parseFieldType(JsDocToken token) {
Node fieldName = parseFieldName(token);
if (fieldName == null) {
return null;
}
skipEOLs();
if (!match(JsDocToken.COLON)) {
return fieldName;
}
// Move to the colon.
next();
// Move to the token after the colon and parse
// the type expression.
skipEOLs();
Node typeExpression = parseTypeExpression(next());
if (typeExpression == null) {
return null;
}
Node fieldType = newNode(Token.COLON);
fieldType.addChildToBack(fieldName);
fieldType.addChildToBack(typeExpression);
return fieldType;
}
/**
* FieldName := NameExpression | StringLiteral | NumberLiteral |
* ReservedIdentifier
*/
private Node parseFieldName(JsDocToken token) {
switch (token) {
case STRING:
String string = stream.getString();
return newStringNode(string);
default:
return null;
}
}
private Node wrapNode(int type, Node n) {
return n == null ?
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> null :
new Node(type, n, stream.getLineno(), stream.getCharno());
}
private Node newNode(int type) {
return new Node(type, stream.getLineno(), stream.getCharno());
}
private Node newStringNode(String s) {
return Node.newString(s, stream.getLineno(), stream.getCharno());
}
private Node reportTypeSyntaxWarning(String warning) {
parser.addWarning(warning, stream.getLineno(), stream.getCharno());
return null;
}
private Node reportGenericTypeSyntaxWarning() {
return reportTypeSyntaxWarning("msg.jsdoc.type.syntax");
}
/**
* Eats tokens until {@link JsDocToken#EOL} included, and switches back the
* state to {@link State#SEARCHING_ANNOTATION}.
*/
private JsDocToken eatTokensUntilEOL() {
return eatTokensUntilEOL(next());
}
/**
* Eats tokens until {@link JsDocToken#EOL} included, and switches back the
* state to {@link State#SEARCHING_ANNOTATION}.
*/
private JsDocToken eatTokensUntilEOL(JsDocToken token) {
do {
if (token == JsDocToken.EOL || token == JsDocToken.EOC ||
token == JsDocToken.EOF) {
state = State.SEARCHING_ANNOTATION;
return token;
}
token = next();
} while (true);
}
/**
* Specific value indicating that the {@link #unreadToken} contains no token.
*/
private static final JsDocToken NO_UNREAD_TOKEN = null;
/**
* One token buffer.
*/
private JsDocToken unreadToken = NO_UNREAD_TOKEN;
/** Restores the lookahead token to the token stream */
private void restoreLookAhead(JsDocToken token) {
unreadToken = token;
}
/**
* Tests whether the next symbol of the token stream matches the specific
* token.
*/
private boolean match(JsDocToken token) {
unreadToken = next();
return unreadToken == token;
}
/**
* Tests that the next symbol of the token stream matches one of the specified
* tokens.
*/
private boolean match(JsDoc
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>Token token1, JsDocToken token2) {
unreadToken = next();
return unreadToken == token1 || unreadToken == token2;
}
/**
* Gets the next token of the token stream or the buffered token if a matching
* was previously made.
*/
private JsDocToken next() {
if (unreadToken == NO_UNREAD_TOKEN) {
return stream.getJsDocToken();
} else {
return current();
}
}
/**
* Gets the current token, invalidating it in the process.
*/
private JsDocToken current() {
JsDocToken t = unreadToken;
unreadToken = NO_UNREAD_TOKEN;
return t;
}
/**
* Skips all EOLs and all empty lines in the JSDoc. Call this method if you
* want the JSDoc entry to span multiple lines.
*/
private void skipEOLs() {
while (match(JsDocToken.EOL)) {
next();
if (match(JsDocToken.STAR)) {
next();
}
}
}
/**
* Determines whether the parser has been populated with docinfo with a
* fileoverview tag.
*/
private boolean hasParsedFileOverviewDocInfo() {
return jsdocBuilder.isPopulatedWithFileOverview();
}
boolean hasParsedJSDocInfo() {
return jsdocBuilder.isPopulated();
}
JSDocInfo retrieveAndResetParsedJSDocInfo() {
return jsdocBuilder.build(sourceName);
}
/**
* Gets the fileoverview JSDocInfo, if any.
*/
JSDocInfo getFileOverviewJSDocInfo() {
return fileOverviewJSDocInfo;
}
}
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name) node);
case Token.NEW:
return processNewExpression((NewExpression) node);
case Token.NUMBER:
return processNumberLiteral((NumberLiteral) node);
case Token.OBJECTLIT:
return processObjectLiteral((ObjectLiteral) node);
case Token.REGEXP:
return processRegExpLiteral((RegExpLiteral) node);
case Token.RETURN:
return processReturnStatement((ReturnStatement) node);
case Token.SCRIPT:
return processAstRoot((AstRoot) node);
case Token.STRING:
return processStringLiteral((StringLiteral) node);
case Token.SWITCH:
return processSwitchStatement((SwitchStatement) node);
case Token.THROW:
return processThrowStatement((ThrowStatement) node);
case Token.TRY:
return processTryStatement((TryStatement) node);
case Token.VAR:
if (node instanceof VariableDeclaration) {
return processVariableDeclaration((VariableDeclaration) node);
} else if (node instanceof VariableInitializer) {
return processVariableInitializer((VariableInitializer) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.WHILE:
return processWhileLoop((WhileLoop) node);
case Token.WITH:
return processWithStatement((WithStatement) node);
}
return processIllegalToken(node);
}
}
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> * Gets the boolean value of a node that represents a literal. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
*
* @throws IllegalArgumentException If {@code n} is not a literal value
*/
static TernaryValue getBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return TernaryValue.forBoolean(n.getString().length() > 0);
case Token.NUMBER:
return TernaryValue.forBoolean(n.getDouble() != 0);
case Token.NULL:
case Token.FALSE:
case Token.VOID:
return TernaryValue.FALSE;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return TernaryValue.FALSE;
} else if ("Infinity".equals(name)) {
return TernaryValue.TRUE;
}
break;
case Token.TRUE:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
return TernaryValue.TRUE;
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): Convert constant array, object, and regex literals as well.
switch (n.getType()) {
case Token.NAME:
case Token.STRING:
return n.getString();
case Token.NUMBER:
double value = n.getDouble();
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(n.getDouble());
}
case Token.FALSE:
case Token.TRUE:
case Token.NULL:
return Node.tokenToName(n.getType());
case Token.VOID:
return "undefined";
}
return null;
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* function() { return a; } is not.
*/
static boolean isLiteralValue(Node n) {
// TODO(nicksantos): Refine this function to catch more literals.
switch (n.getType()) {
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child)) {
return false;
}
}
return true;
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Single operators are valid if the child is valid.
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.NOT:
case Token.NEG:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (block.getType() != Token.BLOCK) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (n.getType() != Token.EMPTY) {
return false;
}
}
return true;
}
/**
* A "simple" operator is one whose children are expressions,
* has no direct side
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.LP:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null)
return true;
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>:
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.ARRAYLIT:
case Token.CALL:
case Token.EMPTY:
case Token.FALSE:
case Token.FUNCTION:
case Token.GETELEM:
case Token.GETPROP:
case Token.GET_REF:
case Token.IF:
case Token.LP:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.RETURN:
case Token.STRING:
case Token.THIS:
case Token.TRUE:
return 15;
default: throw new Error("Unknown precedence for " +
Node.tokenToName(type) +
" (type " + type + ")");
}
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatentation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BIT
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> Token.BLOCK);
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else {
return false;
}
}
/**
* Is this a CALL node?
*/
static boolean isCall(Node n) {
return n.getType() == Token.CALL;
}
/**
* Is this a FUNCTION node?
*/
static boolean isFunction(Node n) {
return n.getType() == Token.FUNCTION;
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(isFunction(fn));
return fn.getLastChild();
}
/**
* Is this a THIS node?
*/
static boolean isThis(Node node) {
return node.getType() == Token.THIS;
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.getType() == Token.FUNCTION && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().getType() == Token.SCRIPT
|| n.getParent().getParent().getType() == Token.FUNCTION);
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>apply'](...)
*/
static boolean isFunctionObjectApply(Node callNode) {
return isObjectCallMethod(callNode, "apply");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
* where x is a NAME node.
*/
static boolean isSimpleFunctionObjectCall(Node callNode) {
if (isFunctionObjectCall(callNode)) {
if (callNode.getFirstChild().getFirstChild().getType() == Token.NAME) {
return true;
}
}
return false;
}
/**
* Determines whether this node is strictly on the left hand side of an assign
* or var initialization. Notably, this does not include all L-values, only
* statements where the node is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isLhs(Node n, Node parent) {
return (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) ||
parent.getType() == Token.VAR;
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
if (node.getType() == Token.STRING && parent.getType() == Token.OBJECTLIT) {
int index = 0;
for (Node current = parent.getFirstChild();
current != null;
current = current.getNext()) {
if (current == node) {
return index % 2 == 0;
}
index++;
}
}
return false;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String opToStr(int operator) {
switch (operator) {
case Token.
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>0"
*/
static Node newUndefinedNode(Node srcReferenceNode) {
// TODO(johnlenz): Why this instead of the more common "undefined"?
Node node = new Node(Token.VOID, Node.newNumber(0));
if (srcReferenceNode != null) {
node.copyInformationFromForTree(srcReferenceNode);
}
return node;
}
/**
* Create a VAR node containing the given name and initial value expression.
*/
static Node newVarNode(String name, Node value) {
Node nodeName = Node.newString(Token.NAME, name);
if (value != null) {
Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.copyInformationFrom(value);
}
Node var = new Node(Token.VAR, nodeName)
.copyInformationFrom(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
public boolean apply(Node n) {
return n.getType() == Token.NAME
&& n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.getType() == Token.VAR;
}
}
/**
* A predicate for matching anything except function nodes.
*/
static class MatchNotFunction implements Predicate<Node>{
public boolean apply(Node n) {
return !isFunction(n);
}
}
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
public boolean apply(Node n) {
Node parent = n.getParent();
return n.getType() ==
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>, and non-controversial. #3 is
* a bit trickier. It means that if you have:
* <code>
* /** @param {number} x /
* Foo.prototype.bar = goog.abstractMethod;
* </code>
* the JSDocInfo will appear in two places in the type system: in the 'bar'
* slot of Foo.prototype, and on the function expression type created by
* this expression.
*
* @author nicksantos@google.com (Nick Santos)
*/
class InferJSDocInfo extends AbstractPostOrderCallback
implements CompilerPass {
private AbstractCompiler compiler;
private boolean inExterns;
InferJSDocInfo(AbstractCompiler compiler) {
this.compiler = compiler;
}
public void process(Node externs, Node root) {
if (externs != null) {
inExterns = true;
NodeTraversal.traverse(compiler, externs, this);
}
if (root != null) {
inExterns = false;
NodeTraversal.traverse(compiler, root, this);
}
}
public void visit(NodeTraversal t, Node n, Node parent) {
JSDocInfo docInfo;
switch (n.getType()) {
// Infer JSDocInfo on types of all type declarations on variables.
case Token.NAME:
if (parent == null) {
return;
}
// Only allow JSDoc on VARs, function declarations, and assigns.
if (parent.getType() != Token.VAR &&
!NodeUtil.isFunctionDeclaration(parent) &&
!(parent.getType() == Token.ASSIGN &&
n == parent.getFirstChild())) {
return;
}
// There are four places the doc info could live.
// 1) A FUNCTION node.
// /** ... */ function f() { ... }
// 2) An ASSIGN parent.
// /** ... */ x = function () { ... }
// 3) A NAME parent.
// var x, /** ... */ y = function() { ... }
// 4) A VAR gramps.
// /** ... */ var x = function() { ... }
docInfo = n.getJSDocInfo();
if (docInfo == null &&
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.DEBUGGER:
Preconditions.checkState(childCount == 0);
add("debugger");
cc.endStatement();
break;
case Token.BREAK:
Preconditions.checkState(childCount <= 1);
add("break");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.EXPR_VOID:
throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT.");
case Token.EXPR_RESULT:
Preconditions.checkState(childCount == 1);
add(first, Context.START_OF_EXPR);
cc.endStatement();
break;
case Token.NEW:
add("new ");
int precedence = NodeUtil.precedence(type);
// If the first child contains a CALL, then claim higher precedence
// to force parens. Otherwise, when parsed, NEW will bind to the
// first viable parens
if (NodeUtil.containsCall(first)) {
precedence = NodeUtil.precedence(first.getType()) + 1;
}
addExpr(first, precedence);
// '()' is optional when no arguments are present
Node next = first.getNext();
if (next != null) {
add("(");
addList(next);
add(")");
}
break;
case Token.STRING:
Preconditions.checkState(childCount == 0);
add(jsString(n.getString(), outputCharsetEncoder));
break;
case Token.DELPROP:
Preconditions.checkState(childCount == 1);
add("delete ");
add(first);
break;
case Token.OBJECTLIT: {
Preconditions.checkState(childCount % 2 == 0);
boolean needsParens = (context == Context.START_OF_EXPR);
if (needsParens
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> MAX_UNION_SIZE) {
return this;
}
// Look through the alternates we've got so far,
// and check if any of them are duplicates of
// one another.
Iterator<JSType> it = alternates.iterator();
while (it.hasNext()) {
JSType current = it.next();
if (alternate.isUnknownType() ||
current.isUnknownType()) {
if (alternate.isEquivalentTo(current)) {
// Alternate is unnecessary.
return this;
}
} else {
if (alternate.isSubtype(current)) {
// Alternate is unnecessary.
return this;
} else if (current.isSubtype(alternate)) {
// Alternate makes current obsolete
it.remove();
}
}
}
alternates.add(alternate);
result = null; // invalidate the memoized result
}
} else {
result = null;
}
return this;
}
/**
* Creates a union.
* @return A UnionType if it has two or more alternates, the
* only alternate if it has one and otherwise {@code NO_TYPE}.
*/
JSType build() {
if (result == null) {
if (isAllType) {
result = registry.getNativeType(ALL_TYPE);
} else if (isNativeUnknownType) {
if (areAllUnknownsChecked) {
result = registry.getNativeType(CHECKED_UNKNOWN_TYPE);
} else {
result = registry.getNativeType(UNKNOWN_TYPE);
}
} else {
int size = alternates.size();
if (size > MAX_UNION_SIZE) {
result = registry.getNativeType(UNKNOWN_TYPE);
} else {
if (size > 1) {
result = new UnionType(registry, getAlternateListCopy());
} else if (size == 1) {
result = alternates.iterator().next();
} else {
result = registry.getNativeType(NO_TYPE);
}
}
}
}
return result;
}
private static final Comparator<JSType> typeSorter =
new Comparator<JSType>() {
@Override public int compare(JSType a, JSType b) {
return b.hashCode() - a.hashCode();
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> the function that we're assigning to.
* If null, that just means we're not initializing this to a function
* literal.
*/
FunctionTypeBuilder inferFromOverriddenFunction(
@Nullable FunctionType oldType, @Nullable Node paramsParent) {
if (oldType == null) {
return this;
}
returnType = oldType.getReturnType();
returnTypeInferred = oldType.isReturnTypeInferred();
if (paramsParent == null) {
// Not a function literal.
parametersNode = oldType.getParametersNode();
if (parametersNode == null) {
parametersNode = new FunctionParamBuilder(typeRegistry).build();
}
} else {
// We're overriding with a function literal. Apply type information
// to each parameter of the literal.
FunctionParamBuilder paramBuilder =
new FunctionParamBuilder(typeRegistry);
Iterator<Node> oldParams = oldType.getParameters().iterator();
boolean warnedAboutArgList = false;
boolean oldParamsNodeHasVarArgs = false;
for (Node currentParam = paramsParent.getFirstChild();
currentParam != null; currentParam = currentParam.getNext()) {
if (oldParams.hasNext()) {
Node oldParam = oldParams.next();
Node newParam = paramBuilder.newParameterFromNode(oldParam);
// The subclass method might right its var_args as individual
// arguments.
if (currentParam.getNext() != null && newParam.isVarArgs()) {
newParam.setVarArgs(false);
newParam.setOptionalArg(true);
oldParamsNodeHasVarArgs = true;
}
} else {
warnedAboutArgList |= addParameter(
paramBuilder,
typeRegistry.getNativeType(UNKNOWN_TYPE),
warnedAboutArgList,
codingConvention.isOptionalParameter(currentParam) ||
oldParamsNodeHasVarArgs,
codingConvention.isVarArgsParameter(currentParam));
}
}
parametersNode = paramBuilder.build();
}
return this;
}
/**
* Infer the return type from JSDocInfo.
*/
FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) {
if (info != null && info.hasReturnType()) {
returnType = info.getReturnType().evaluate(scope, typeRegistry);
returnTypeInferred =
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>BadModuleReference(name, ref);
}
}
}
}
}
private void reportBadModuleReference(Name name, Ref ref) {
compiler.report(
JSError.make(ref.sourceName, ref.node, STRICT_MODULE_DEP_QNAME,
ref.module.getName(), name.declaration.module.getName(),
name.fullName()));
}
private void reportRefToUndefinedName(Name name, Ref ref) {
// grab the highest undefined ancestor to output in the warning message.
while (name.parent != null &&
name.parent.globalSets + name.parent.localSets == 0) {
name = name.parent;
}
// If this is an annotated EXPR-GET, don't do anything.
Node parent = ref.node.getParent();
if (parent.getType() == Token.EXPR_RESULT) {
JSDocInfo info = ref.node.getJSDocInfo();
if (info != null && info.hasTypedefType()) {
return;
}
}
compiler.report(
JSError.make(ref.sourceName, ref.node, level, UNDEFINED_NAME_WARNING,
name.fullName()));
}
/**
* Checks whether the given name is a property, and whether that property
* must be initialized with its full qualified name.
*/
private static boolean propertyMustBeInitializedByFullName(Name name) {
// If an object literal in the global namespace is never aliased,
// then all of its properties must be defined using its full qualified
// name. This implies that its properties must all be in the global
// namespace as well.
//
// The same is not true for FUNCTION and OTHER types, because their
// implicit prototypes have properties that are not captured by the global
// namespace.
return name.parent != null && name.parent.aliasingGets == 0 &&
name.parent.type == Name.Type.OBJECTLIT;
}
}
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>SingletonGetter".equals(callName)) ||
callNode.getChildCount() != 2) {
return null;
}
return callArg.getNext().getQualifiedName();
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
functionType.defineDeclaredProperty("getInstance", getterType, false);
functionType.defineDeclaredProperty("instance_", objectType, false);
}
@Override
public String getGlobalObject() {
return "goog.global";
}
private final Set<String> propertyTestFunctions = ImmutableSet.of(
"goog.isDef", "goog.isNull", "goog.isDefAndNotNull",
"goog.isString", "goog.isNumber", "goog.isBoolean",
"goog.isFunction", "goog.isArray", "goog.isObject");
@Override
public boolean isPropertyTestFunction(Node call) {
Preconditions.checkArgument(call.getType() == Token.CALL);
return propertyTestFunctions.contains(
call.getFirstChild().getQualifiedName());
}
@Override
public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t,
Node callNode) {
Preconditions.checkArgument(callNode.getType() == Token.CALL);
Node callName = callNode.getFirstChild();
if (!"goog.reflect.object".equals(callName.getQualifiedName()) ||
callName.getChildCount() != 2) {
return null;
}
Node typeNode = callName.getNext();
if (!typeNode.isQualifiedName()) {
return null;
}
Node objectNode = typeNode.getNext();
if (objectNode.getType() != Token.OBJECTLIT) {
t.getCompiler().report(JSError.make(t.getSourceName(), callNode,
OBJECTLIT_EXPECTED));
return null;
}
return new ObjectLiteralCast(typeNode.getQualifiedName(),
typeNode.getNext());
}
@Override
public boolean isOptionalParameter(Node parameter) {
return false;
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return false;
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return ImmutableList.<AssertionFunctionSpec>
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> The
* QUOTED_PROP int property is only assigned to STRING tokens used as
* object lit keys.
* @return true if this was a quoted string key in an object literal.
*/
@Override
public boolean isQuotedString() {
return getBooleanProp(QUOTED_PROP);
}
/**
* This should only be called for STRING nodes created in object lits.
*/
@Override
public void setQuotedString() {
putBooleanProp(QUOTED_PROP, true);
}
private String str;
}
// PropListItems are immutable so that they can be shared.
private static class PropListItem implements Serializable {
private static final long serialVersionUID = 1L;
final PropListItem next;
final int type;
final int intValue;
final Object objectValue;
PropListItem(int type, int intValue, PropListItem next) {
this(type, intValue, null, next);
}
PropListItem(int type, Object objectValue, PropListItem next) {
this(type, 0, objectValue, next);
}
PropListItem(
int type, int intValue, Object objectValue, PropListItem next) {
this.type = type;
this.intValue = intValue;
this.objectValue = objectValue;
this.next = next;
}
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type = nodeType;
parent = null;
first = last = child;
child.next = null;
child.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node right) {
Preconditions.checkArgument(left.parent == null,
"first new child has existing parent");
Preconditions.checkArgument(left.next == null,
"first new child has existing sibling");
Preconditions.checkArgument(right.parent == null,
"second new child has existing parent");
Preconditions.checkArgument(right.next == null,
"second
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> new child has existing sibling");
type = nodeType;
parent = null;
first = left;
last = right;
left.next = right;
left.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = right;
mid.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(mid2.parent == null);
Preconditions.checkArgument(mid2.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = mid2;
mid.parent = this;
mid2.next = right;
mid2.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, int lineno, int charno) {
type = nodeType;
parent = null;
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node child, int lineno, int charno) {
this(nodeType, child);
sourcePosition = merge
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>LineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node right, int lineno, int charno) {
this(nodeType, left, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node right,
int lineno, int charno) {
this(nodeType, left, mid, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right,
int lineno, int charno) {
this(nodeType, left, mid, mid2, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children, int lineno, int charno) {
this(nodeType, children);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children) {
this.type = nodeType;
parent = null;
if (children.length != 0) {
this.first = children[0];
this.last = children[children.length - 1];
for (int i = 1; i < children.length; i++) {
if (null != children[i - 1].next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first) {
return null;
}
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null) {
throw new RuntimeException("node is not a child");
}
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while (i > 0) {
n = n.next;
i--;
}
return n;
}
public Node getLastSibling() {
Node n = this;
while (n.next != null) {
n = n.next;
}
return n;
}
public void addChildToFront(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = first;
first = child;
if (last == null) {
last = child;
}
}
public void addChildToBack(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = null;
if (last == null) {
first = last = child;
return;
}
last.next =
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> child;
last = child;
}
public void addChildrenToFront(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
Node lastSib = children.getLastSibling();
lastSib.next = first;
first = children;
if (last == null) {
last = lastSib;
}
}
public void addChildrenToBack(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
if (last != null) {
last.next = children;
}
last = children.getLastSibling();
if (first == null) {
first = children;
}
}
/**
* Add 'child' before 'node'.
*/
public void addChildBefore(Node newChild, Node node) {
Preconditions.checkArgument(node != null,
"The existing child node of the parent should not be null.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
if (first == node) {
newChild.parent = this;
newChild.next = first;
first = newChild;
return;
}
Node prev = getChildBefore(node);
addChildAfter(newChild, prev);
}
/**
* Add 'child' after 'node'.
*/
public void addChildAfter(Node newChild, Node node) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
newChild.parent = this;
newChild.next = node.next;
node.next = newChild;
if (last == node) {
last = newChild;
}
}
/**
* Detach a child from its parent and siblings.
*/
public void removeChild(Node child) {
Node prev = getChild
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>Before(child);
if (prev == null)
first = first.next;
else
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
}
/**
* Detaches child from Node and replaces it with newChild.
*/
public void replaceChild(Node child, Node newChild) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(child);
newChild.next = child.next;
newChild.parent = this;
if (child == first) {
first = newChild;
} else {
Node prev = getChildBefore(child);
prev.next = newChild;
}
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
public void replaceChildAfter(Node prevChild, Node newChild) {
Preconditions.checkArgument(prevChild.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(prevChild);
Node child = prevChild.next;
newChild.next = child.next;
newChild.parent = this;
prevChild.next = newChild;
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
@VisibleForTesting
PropListItem lookupProperty(int propType) {
PropListItem x = propListHead;
while (x != null && propType != x.type) {
x = x.next;
}
return x;
}
/**
* Clone the properties from the provided node without copying
* the property object. The recieving node may not have any
* existing properties.
* @param other The node to clone properties from.
*
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> @return this node.
*/
public Node clonePropsFrom(Node other) {
Preconditions.checkState(this.propListHead == null,
"Node has existing properties.");
this.propListHead = other.propListHead;
return this;
}
public void removeProp(int propType) {
PropListItem result = removeProp(propListHead, propType);
if (result != propListHead) {
propListHead = result;
}
}
/**
* @param item The item to inspect
* @param propType The property to look for
* @return The replacement list if the property was removed, or
* 'item' otherwise.
*/
private PropListItem removeProp(PropListItem item, int propType) {
if (item == null) {
return null;
} else if (item.type == propType) {
return item.next;
} else {
PropListItem result = removeProp(item.next, propType);
if (result != item.next) {
return new PropListItem(
item.type, item.intValue, item.objectValue, result);
} else {
return item;
}
}
}
public Object getProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return null;
}
return item.objectValue;
}
public boolean getBooleanProp(int propType) {
return getIntProp(propType) != 0;
}
/**
* Returns the integer value for the property, or 0 if the property
* is not defined.
*/
public int getIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return 0;
}
return item.intValue;
}
public int getExistingIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
Kit.codeBug();
}
return item.intValue;
}
public void putProp(int propType, Object value) {
removeProp(propType);
if (value != null) {
propListHead = new PropListItem(propType, value, propListHead);
}
}
public void
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> putBooleanProp(int propType, boolean value) {
putIntProp(propType, value ? 1 : 0);
}
public void putIntProp(int propType, int value) {
removeProp(propType);
if (value != 0) {
propListHead = new PropListItem(propType, value, propListHead);
}
}
// Gets all the property types, in sorted order.
private int[] getSortedPropTypes() {
int count = 0;
for (PropListItem x = propListHead; x != null; x = x.next) {
count++;
}
int[] keys = new int[count];
for (PropListItem x = propListHead; x != null; x = x.next) {
count--;
keys[count] = x.type;
}
Arrays.sort(keys);
return keys;
}
public int getLineno() {
return extractLineno(sourcePosition);
}
public int getCharno() {
return extractCharno(sourcePosition);
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public double getDouble() throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a number node");
}
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public void setDouble(double s) throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public String getString() throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public void setString(String s) throws UnsupportedOperationException {
if (this.getType() ==
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> throws IOException {
toStringTreeHelper(this, 0, appendable);
}
private static void toStringTreeHelper(Node n, int level, Appendable sb)
throws IOException {
if (Token.printTrees) {
for (int i = 0; i != level; ++i) {
sb.append(" ");
}
sb.append(n.toString());
sb.append('\n');
for (Node cursor = n.getFirstChild();
cursor != null;
cursor = cursor.getNext()) {
toStringTreeHelper(cursor, level + 1, sb);
}
}
}
int type; // type of the node; Token.NAME for example
Node next; // next sibling
private Node first; // first element of a linked list of children
private Node last; // last element of a linked list of children
/**
* Linked list of properties. Since vast majority of nodes would have
* no more then 2 properties, linked list saves memory and provides
* fast lookup. If this does not holds, propListHead can be replaced
* by UintMap.
*/
private PropListItem propListHead;
/**
* COLUMN_BITS represents how many of the lower-order bits of
* sourcePosition are reserved for storing the column number.
* Bits above these store the line number.
* This gives us decent position information for everything except
* files already passed through a minimizer, where lines might
* be longer than 4096 characters.
*/
public static final int COLUMN_BITS = 12;
/**
* MAX_COLUMN_NUMBER represents the maximum column number that can
* be represented. JSCompiler's modifications to Rhino cause all
* tokens located beyond the maximum column to MAX_COLUMN_NUMBER.
*/
public static final int MAX_COLUMN_NUMBER = (1 << COLUMN_BITS) - 1;
/**
* COLUMN_MASK stores a value where bits storing the column number
* are set, and bits storing the line are not set. It's handy for
* separating column number from line number.
*/
public static final int COLUMN_MASK = MAX_COLUMN_NUMBER;
/**
* Source position of this node. The position is encoded with the
* column number in the low 12 bits of the integer, and
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> nodes's children.
* The iterator does not support the optional operation
* {@link Iterator#remove()}.</p>
*
* <p>To iterate over a node's siblings, one can write</p>
* <pre>Node n = ...;
* for (Node child : n.children()) { ...</pre>
*/
public Iterable<Node> children() {
if (first == null) {
return Collections.emptySet();
} else {
return new SiblingNodeIterable(first);
}
}
/**
* <p>Return an iterable object that iterates over this nodes's siblings.
* The iterator does not support the optional operation
* {@link Iterator#remove()}.</p>
*
* <p>To iterate over a node's siblings, one can write</p>
* <pre>Node n = ...;
* for (Node sibling : n.siblings()) { ...</pre>
*/
public Iterable<Node> siblings() {
return new SiblingNodeIterable(this);
}
/**
* @see Node#siblings()
*/
private static final class SiblingNodeIterable
implements Iterable<Node>, Iterator<Node> {
private final Node start;
private Node current;
private boolean used;
SiblingNodeIterable(Node start) {
this.start = start;
this.current = start;
this.used = false;
}
public Iterator<Node> iterator() {
if (!used) {
used = true;
return this;
} else {
// We have already used the current object as an iterator;
// we must create a new SiblingNodeIterable based on this
// iterable's start node.
//
// Since the primary use case for Node.children is in for
// loops, this branch is extremely unlikely.
return (new SiblingNodeIterable(start)).iterator();
}
}
public boolean hasNext() {
return current != null;
}
public Node next() {
if (current == null) {
throw new NoSuchElementException();
}
try {
return current;
} finally {
current = current.getNext();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
// ==========================================================================
// Accessors
public Node getParent() {
return parent;
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
}
/**
* Gets the ancestor node relative to this.
*
* @param level 0 = this, 1 = the parent, etc.
*/
public Node getAncestor(int level) {
Preconditions.checkArgument(level >= 0);
Node node = this;
while (node != null && level-- > 0) {
node = node.getParent();
}
return node;
}
/**
* Iterates all of the node's ancestors excluding itself.
*/
public AncestorIterable getAncestors() {
return new AncestorIterable(this.getParent());
}
/**
* Iterator to go up the ancestor tree.
*/
public static class AncestorIterable implements Iterable<Node> {
private Node cur;
/**
* @param cur The node to start.
*/
AncestorIterable(Node cur) {
this.cur = cur;
}
public Iterator<Node> iterator() {
return new Iterator<Node>() {
public boolean hasNext() {
return cur != null;
}
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
Node n = cur;
cur = cur.getParent();
return n;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Check for one child more efficiently than by iterating over all the
* children as is done with Node.getChildCount().
*
* @return Whether the node has exactly one child.
*/
public boolean hasOneChild() {
return first != null && first == last;
}
/**
* Check for more than one child more efficiently than by iterating over all
* the children as is done with Node.getChildCount().
*
* @return Whether the node more than one child.
*/
public boolean hasMoreThanOneChild() {
return first != null && first != last;
}
public int getChildCount() {
int c = 0;
for (Node n = first; n != null; n = n.next)
c++;
return c;
}
// Intended for testing and verification only.
public boolean hasChild(Node child) {
for (Node n = first; n != null; n = n.getNext()) {
if (child == n) {
return true
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>;
}
}
return false;
}
/**
* Checks if the subtree under this node is the same as another subtree.
* Returns null if it's equal, or a message describing the differences.
*/
public String checkTreeEquals(Node node2) {
NodeMismatch diff = checkTreeEqualsImpl(node2);
if (diff != null) {
return "Node tree inequality:" +
"\nTree1:\n" + toStringTree() +
"\n\nTree2:\n" + node2.toStringTree() +
"\n\nSubtree1: " + diff.nodeA.toStringTree() +
"\n\nSubtree2: " + diff.nodeB.toStringTree();
}
return null;
}
/**
* If this is a compilation pass and not a test, do not construct error
* strings. Instead return true if the trees are equal.
*/
public boolean checkTreeEqualsSilent(Node node2) {
return checkTreeEqualsImpl(node2) == null;
}
/**
* Helper function to ignore differences in Node subclasses that are no longer
* used.
*/
@SuppressWarnings("unchecked")
static private Class getNodeClass(Node n) {
Class c = n.getClass();
if (c == FunctionNode.class || c == ScriptOrFnNode.class) {
return Node.class;
}
return c;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeEqualsImpl(Node node2) {
boolean eq = false;
if (type == node2.getType() && getChildCount() == node2.getChildCount()
&& getNodeClass(this) == getNodeClass(node2)) {
eq = this.isEquivalentTo(node2);
}
if (!eq) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTree
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>EqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
/**
* Checks if the subtree under this node is the same as another subtree
* including types. Returns null if it's equal, or a message describing the
* differences.
*/
public boolean checkTreeTypeAwareEqualsSilent(Node node2) {
return checkTreeTypeAwareEqualsImpl(node2) == null;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeTypeAwareEqualsImpl(Node node2) {
boolean eq = false;
if (type == node2.getType()
&& getChildCount() == node2.getChildCount()
&& getClass() == node2.getClass()
&& JSType.isEquivalent(jsType, node2.getJSType())) {
eq = this.isEquivalentTo(node2);
}
if (!eq) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTreeTypeAwareEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
public static String tokenToName(int token) {
switch (token) {
case Token.ERROR: return "error";
case Token.EOF: return "eof";
case Token.EOL: return "eol";
case Token.ENTERWITH: return "enterwith";
case Token.LEAVEWITH: return "leavewith";
case Token.RETURN: return "return";
case Token.GOTO: return "goto";
case Token.IFEQ: return "ifeq";
case Token.IFNE: return "ifne";
case Token.SETNAME: return "setname";
case Token.BITOR: return "bitor";
case Token.BITXOR: return "bit
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>BREAK: return "break";
case Token.CONTINUE: return "continue";
case Token.VAR: return "var";
case Token.WITH: return "with";
case Token.CATCH: return "catch";
case Token.FINALLY: return "finally";
case Token.RESERVED: return "reserved";
case Token.NOT: return "not";
case Token.VOID: return "void";
case Token.BLOCK: return "block";
case Token.ARRAYLIT: return "arraylit";
case Token.OBJECTLIT: return "objectlit";
case Token.LABEL: return "label";
case Token.TARGET: return "target";
case Token.LOOP: return "loop";
case Token.EXPR_VOID: return "expr_void";
case Token.EXPR_RESULT: return "expr_result";
case Token.JSR: return "jsr";
case Token.SCRIPT: return "script";
case Token.EMPTY: return "empty";
case Token.GET_REF: return "get_ref";
case Token.REF_SPECIAL: return "ref_special";
}
return "<unknown="+token+">";
}
/** Returns true if this node is equivalent semantically to another */
public boolean isEquivalentTo(Node node) {
if (type == Token.ARRAYLIT) {
try {
int[] indices1 = (int[]) getProp(Node.SKIP_INDEXES_PROP);
int[] indices2 = (int[]) node.getProp(Node.SKIP_INDEXES_PROP);
if (indices1 == null) {
if (indices2 != null) {
return false;
}
} else if (indices2 == null) {
return false;
} else if (indices1.length != indices2.length) {
return false;
} else {
for (int i = 0; i < indices1.length; i++) {
if (indices1[i] != indices2[i]) {
return false;
}
}
}
} catch (Exception e) {
return false;
}
} else if (type == Token.INC || type == Token.DEC) {
int post1 = this.getIntProp(INCRDE
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>CR_PROP);
int post2 = node.getIntProp(INCRDECR_PROP);
if (post1 != post2) {
return false;
}
} else if (type == Token.STRING) {
int quoted1 = this.getIntProp(QUOTED_PROP);
int quoted2 = node.getIntProp(QUOTED_PROP);
if (quoted1 != quoted2) {
return false;
}
}
return true;
}
public boolean hasSideEffects() {
switch (type) {
case Token.EXPR_VOID:
case Token.COMMA:
if (last != null)
return last.hasSideEffects();
else
return true;
case Token.HOOK:
if (first == null || first.next == null || first.next.next == null) {
Kit.codeBug();
}
return first.next.hasSideEffects() && first.next.next.hasSideEffects();
case Token.ERROR: // Avoid cascaded error messages
case Token.EXPR_RESULT:
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ENTERWITH:
case Token.LEAVEWITH:
case Token.RETURN:
case Token.GOTO:
case Token.IFEQ:
case Token.IFNE:
case Token.NEW:
case Token.DELPROP:
case Token.SETNAME:
case Token.SETPROP:
case Token.SETELEM:
case Token.CALL:
case Token.THROW:
case Token.RETHROW:
case Token.SETVAR:
case Token.CATCH_SCOPE:
case Token.RETURN_RESULT:
case Token.SET_REF:
case Token.DEL_REF:
case Token.REF_CALL:
case Token.TRY:
case Token.SEMI:
case Token.INC:
case Token.DEC:
case Token.EXPORT:
case Token.IMPORT:
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null;) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last = null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(prev.next != null,
"no next sibling.");
Node child = prev.next;
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
return child;
}
/**
* @return A detached clone of the Node, specifically excluding its children.
*/
public Node cloneNode() {
Node result;
try {
result = (Node) super.clone();
// PropListItem lists are immutable and
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> can be shared so there is no
// need to clone them here.
result.next = null;
result.first = null;
result.last = null;
result.parent = null;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e.getMessage());
}
return result;
}
/**
* @return A detached clone of the Node and all its children.
*/
public Node cloneTree() {
Node result = cloneNode();
for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) {
Node n2clone = n2.cloneTree();
n2clone.parent = result;
if (result.last != null) {
result.last.next = n2clone;
}
if (result.first == null) {
result.first = n2clone;
}
result.last = n2clone;
}
return result;
}
/**
* Copies source file and name information from the other
* node given to the current node. Used for maintaining
* debug information across node append and remove operations.
* @return this
*/
public Node copyInformationFrom(Node other) {
if (getProp(ORIGINALNAME_PROP) == null) {
putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP));
}
if (getProp(SOURCEFILE_PROP) == null) {
putProp(SOURCEFILE_PROP, other.getProp(SOURCEFILE_PROP));
sourcePosition = other.sourcePosition;
}
return this;
}
/**
* Copies source file and name information from the other node to the
* entire tree rooted at this node.
* @return this
*/
public Node copyInformationFromForTree(Node other) {
copyInformationFrom(other);
for (Node child = getFirstChild();
child != null; child = child.getNext()) {
child.copyInformationFromForTree(other);
}
return this;
}
//==========================================================================
// Custom annotations
public JSType getJSType() {
return jsType;
}
public void setJSType(JSType jsType) {
this.jsType = jsType;
}
public FileLevelJsDocBuilder getJsDocBuilder
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> body }
Node init = forNode.getFirstChild();
Node cond = init.getNext();
Node iter = cond.getNext();
Node body = iter.getNext();
// After initialization, we transfer to the FOR which is in charge of
// checking the condition (for the first time).
createEdge(init, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode));
// The end of the body will have a unconditional branch to our iter
// (handled by calling computeFollowNode of the last instruction of the
// body. Our iter will jump to the forNode again to another condition
// check.
createEdge(iter, Branch.UNCOND, forNode);
connectToPossibleExceptionHandler(init, init);
connectToPossibleExceptionHandler(forNode, cond);
connectToPossibleExceptionHandler(iter, iter);
} else {
// We have for (item in collection) { body }
Node item = forNode.getFirstChild();
Node collection = item.getNext();
Node body = collection.getNext();
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode));
connectToPossibleExceptionHandler(forNode, collection);
}
}
private void handleSwitch(Node node) {
// Transfer to the first non-DEFAULT CASE. if there are none, transfer
// to the DEFAULT or the EMPTY node.
Node next = getNextSiblingOfType(
node.getFirstChild().getNext(), Token.CASE, Token.EMPTY);
if (next != null) { // Has at least one CASE or EMPTY
createEdge(node, Branch.UNCOND, next);
} else { // Has no CASE but possibly a DEFAULT
if (node.getFirstChild().getNext() != null) {
createEdge(node, Branch.UNCOND, node.getFirstChild().getNext());
} else { // No CASE, no DEFAULT
createEdge(node
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>, Branch.UNCOND, computeFollowNode(node));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleCase(Node node) {
// Case is a bit tricky....First it goes into the body if condition is true.
createEdge(node, Branch.ON_TRUE,
node.getFirstChild().getNext());
// Look for the next CASE, skipping over DEFAULT.
Node next = getNextSiblingOfType(node.getNext(), Token.CASE);
if (next != null) { // Found a CASE
Preconditions.checkState(next.getType() == Token.CASE);
createEdge(node, Branch.ON_FALSE, next);
} else { // No more CASE found, go back and search for a DEFAULT.
Node parent = node.getParent();
Node deflt = getNextSiblingOfType(
parent.getFirstChild().getNext(), Token.DEFAULT);
if (deflt != null) { // Has a DEFAULT
createEdge(node, Branch.ON_FALSE, deflt);
} else { // No DEFAULT found, go to the follow of the SWITCH.
createEdge(node, Branch.ON_FALSE, computeFollowNode(node));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmtList(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.getType() == Token.BLOCK && parent != null &&
parent.getType() == Token.TRY &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> control doesn't go into that
// function (unless it is called)
while (child != null && child.getType() == Token.FUNCTION) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.getType() == Token.BLOCK && node.isSyntheticBlock()) {
Node next = node.getLastChild();
if (next != null) {
createEdge(node, Branch.SYN_BLOCK, computeFallThrough(next));
}
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node lastJump;
Node parent = node.getParent();
/*
* Continuously look up the ancestor tree for the BREAK target or the target
* with the corresponding label and connect to it. If along the path we
*
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>();
}
if (lastJump == node) {
createEdge(node, Branch.UNCOND, iter);
} else {
finallyMap.put(lastJump, iter);
}
}
private void handleReturn(Node node) {
Node lastJump = null;
for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) {
Node curHandler = iter.next();
if (NodeUtil.isFunction(curHandler)) {
break;
}
if (NodeUtil.hasFinally(curHandler)) {
if (lastJump == null) {
createEdge(node, Branch.UNCOND, curHandler.getLastChild());
} else {
finallyMap.put(lastJump,
computeFallThrough(curHandler.getLastChild()));
}
lastJump = curHandler;
}
}
if (node.hasChildren()) {
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
if (lastJump == null) {
createEdge(node, Branch.UNCOND, null);
} else {
finallyMap.put(lastJump, null);
}
}
private void handleStmt(Node node) {
// Simply transfer to the next line.
createEdge(node, Branch.UNCOND, computeFollowNode(node));
connectToPossibleExceptionHandler(node, node);
}
private Node computeFollowNode(Node node) {
return computeFollowNode(node, node);
}
/**
* Computes the follow() node of a given node and its parent. There is a side
* effect when calling this function. If this function computed an edge that
* exists a FINALLY, it'll attempt to connect the fromNode to the outer
* FINALLY according to the finallyMap.
*
* @param fromNode The original source node since {@code node} is changed
* during recursion.
* @param node The node that follow() should compute.
*/
private Node computeFollowNode(Node fromNode, Node node) {
/*
* This is the case where:
*
* 1. Parent is null implies that we are transferring control to the end of
* the script.
*
* 2. Parent is a function implies that we are transferring control back to
* the caller of the function.
*
*
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (n.getType() == Token.FUNCTION ||
n.getType() == Token.SCRIPT) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild() || parent == scope.getRootNode();
}
@Override
public abstract void visit(NodeTraversal t, Node n, Node parent);
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
JSType getDeclaredTypeInAnnotation(
NodeTraversal t, Node node, JSDocInfo info) {
return getDeclaredTypeInAnnotation(t.getSourceName(), node, info);
}
JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode = node.getType() == Token.GETPROP ?
node.getFirstChild() : null;
if (info != null) {
if (info.hasType()) {
jsType = info.getType().evaluate(scope, typeRegistry);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS>_VAR_DEF));
}
for (Node name : n.children()) {
defineName(name, n, parent, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, parent,
(info != null) ? info : name.getJSDocInfo());
}
break;
case Token.FUNCTION:
int parentType = parent.getType();
Preconditions.checkState(
(scope.isLocal() || parentType != Token.ASSIGN) &&
parentType != Token.NAME,
"function defined as standalone function when it is being " +
"assigned");
String functionName = n.getFirstChild().getString();
FunctionType functionType = getFunctionType(functionName, n, info,
null);
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
break;
case Token.ASSIGN:
// TODO(nicksantos): We should support direct assignment to a
// prototype, as in:
// Foo.prototype = {
// a: function() { ... },
// b: function() { ... }
// };
// Right now (6/23/08), we understand most of this syntax, but we
// don't tie the "a" and "b" methods to the context of Foo.
Node rvalue = n.getLastChild();
Node lvalue = n.getFirstChild();
info = (info != null) ? info : rvalue.getJSDocInfo();
if (rvalue.getType() == Token.FUNCTION ||
info != null && info.isConstructor()) {
getFunctionType(lvalue.getQualifiedName(), rvalue, info,
lvalue);
} else if (info != null && info.hasEnumParameterType()) {
JSType type = getEnumType(lvalue.getQualifiedName(), n, rvalue,
info.getEnumParameterType().evaluate(scope, typeRegistry));
if (type != null) {
setDeferredType(lvalue, type);
}
}
break;
default:
throw new IllegalStateException(Integer.toString(n.getType()));
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param
Closure, 147
<FILEB>
<CHANGES>
pType == Token.ASSIGN ||
pType == Token.OBJECTLIT)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
for (Node next = block.getFirstChild();
next!= null && NodeUtil.isFunctionDeclaration(next);
next = next.getNext()) {
insertionPoint = next;
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
<CHANGES>
pType == Token.ASSIGN)) {
<CHANGEE>
return false;
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
<FILEE>
<FILEB>
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isFunction(n)) {
visitFunction(t, n);
} else if (n.getType() == Token.RETURN) {
visitReturn(t, n);
}
}
/**
* Insert checks for the parameters of the function.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType funType = (FunctionType) n.getJSType();
Node block = n.getLastChild();
Node paramName = NodeUtil.getFnParameters(n).getFirstChild();
Node insertionPoint = null;
// To satisfy normalization constraints, the type checking must be
// added after any inner function declarations.
<CHANGES>
<CHANGEE>
for (Node paramType : funType.getParameters()) {
// Can this ever happen?
if (paramName == null) {
return;
}
Node checkNode = createCheckTypeCallNode(<SCANS> JSType elementsType) {
EnumType enumType = null;
// no value with @enum
if (value != null) {
if (value.getType() == Token.OBJECTLIT) {
// collect enum elements
enumType = typeRegistry.createEnumType(name, elementsType);
// populate the enum type.
Node key = value.getFirstChild();
while (key != null) {
String keyName = NodeUtil.getStringValue(key);
if (enumType.hasOwnProperty(keyName)) {
compiler.report(JSError.make(sourceName, key, ENUM_DUP, keyName));
} else if (!codingConvention.isValidEnumKey(keyName)) {
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else {
enumType.defineElement(keyName);
}
key = key.getNext();
key = (key == null) ? null : key.getNext();
}
} else if (value.isQualifiedName()) {
Var var = scope.getVar(value.getQualifiedName());
if (var != null && var.getType() instanceof EnumType) {
enumType = (EnumType) var.getType();
}
}
}
if (enumType == null) {
compiler.report(JSError.make(sourceName, parent, ENUM_INITIALIZER));
} else if (scope.isGlobal()) {
if (name != null && !name.isEmpty()) {
typeRegistry.declareType(name, enumType.getElementsType());
}
}
return enumType;
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type or {@code null} if its type is inferred.
* @param name the defining node. It must be a {@link Token#NAME}.
* @param parent the {@code name}'s parent.
* @param type the variable's type. It may be {@code null}, in which case
* the variable's type will be inferred.
*/
private void defineSlot(Node name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type